|
| 1 | +# Extern Declarations in Husk |
| 2 | + |
| 3 | +Extern declarations allow Husk programs to interface with JavaScript libraries and APIs, particularly object-oriented ones. This feature enables seamless integration with the JavaScript ecosystem while maintaining type safety. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Extern declarations provide a way to: |
| 8 | +- Declare types and functions that exist in JavaScript |
| 9 | +- Define method signatures for object-oriented APIs |
| 10 | +- Maintain type safety when calling external code |
| 11 | +- Support both functional and object-oriented JavaScript patterns |
| 12 | + |
| 13 | +## Basic Syntax |
| 14 | + |
| 15 | +### Extern Functions |
| 16 | + |
| 17 | +Simple extern functions can be declared at the top level: |
| 18 | + |
| 19 | +```husk |
| 20 | +extern fn parseInt(s: string) -> int; |
| 21 | +extern fn parseFloat(s: string) -> float; |
| 22 | +extern fn setTimeout(callback: fn(), delay: int); |
| 23 | +``` |
| 24 | + |
| 25 | +### Extern Modules |
| 26 | + |
| 27 | +For organizing related functionality, use extern modules: |
| 28 | + |
| 29 | +```husk |
| 30 | +extern mod console { |
| 31 | + fn log(message: string); |
| 32 | + fn error(message: string); |
| 33 | + fn warn(message: string); |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +### Extern Types |
| 38 | + |
| 39 | +Declare opaque types that represent JavaScript objects: |
| 40 | + |
| 41 | +```husk |
| 42 | +extern mod express { |
| 43 | + type Application; |
| 44 | + type Request; |
| 45 | + type Response; |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +## Object-Oriented API Support |
| 50 | + |
| 51 | +### Implementing Methods on Extern Types |
| 52 | + |
| 53 | +The most powerful feature of extern declarations is the ability to define methods on extern types using `impl` blocks: |
| 54 | + |
| 55 | +```husk |
| 56 | +extern mod express { |
| 57 | + type Application; |
| 58 | + type Request; |
| 59 | + type Response; |
| 60 | + |
| 61 | + impl Application { |
| 62 | + fn get(self, path: string, handler: fn(Request, Response)); |
| 63 | + fn post(self, path: string, handler: fn(Request, Response)); |
| 64 | + fn listen(self, port: int, callback: fn()); |
| 65 | + } |
| 66 | + |
| 67 | + impl Response { |
| 68 | + fn send(self, data: string); |
| 69 | + fn json(self, data: any); |
| 70 | + fn status(self, code: int) -> Response; |
| 71 | + } |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +### The `self` Parameter |
| 76 | + |
| 77 | +Methods in extern impl blocks can use `self` as the first parameter to indicate instance methods: |
| 78 | + |
| 79 | +- `self` represents the object instance the method is called on |
| 80 | +- `self` doesn't require a type annotation in extern declarations |
| 81 | +- Methods without `self` are treated as static methods |
| 82 | + |
| 83 | +## Complete Example: Express.js Integration |
| 84 | + |
| 85 | +Here's a complete example showing how to use Express.js with Husk: |
| 86 | + |
| 87 | +```husk |
| 88 | +// Import the express default export |
| 89 | +use external::express::default; |
| 90 | +
|
| 91 | +// Declare the express function and types |
| 92 | +extern fn express() -> express::Application; |
| 93 | +
|
| 94 | +extern mod express { |
| 95 | + type Application; |
| 96 | + type Request; |
| 97 | + type Response; |
| 98 | + |
| 99 | + impl Application { |
| 100 | + fn get(self, path: string, handler: fn(Request, Response)); |
| 101 | + fn post(self, path: string, handler: fn(Request, Response)); |
| 102 | + fn put(self, path: string, handler: fn(Request, Response)); |
| 103 | + fn delete(self, path: string, handler: fn(Request, Response)); |
| 104 | + fn use_middleware(self, middleware: fn(Request, Response, fn())); |
| 105 | + fn listen(self, port: int, callback: fn()); |
| 106 | + } |
| 107 | + |
| 108 | + impl Request { |
| 109 | + fn param(self, name: string) -> string; |
| 110 | + fn query(self, name: string) -> string; |
| 111 | + fn body(self) -> any; |
| 112 | + } |
| 113 | + |
| 114 | + impl Response { |
| 115 | + fn send(self, data: string); |
| 116 | + fn json(self, data: any); |
| 117 | + fn status(self, code: int) -> Response; |
| 118 | + fn redirect(self, url: string); |
| 119 | + } |
| 120 | +} |
| 121 | +
|
| 122 | +fn main() { |
| 123 | + let app = express(); |
| 124 | + |
| 125 | + // Define routes using method calls |
| 126 | + app.get("/", |req: express::Request, res: express::Response| { |
| 127 | + res.send("Hello, World!"); |
| 128 | + }); |
| 129 | + |
| 130 | + app.post("/api/users", |req: express::Request, res: express::Response| { |
| 131 | + res.json({ message: "User created", data: req.body() }); |
| 132 | + }); |
| 133 | + |
| 134 | + app.get("/users/:id", |req: express::Request, res: express::Response| { |
| 135 | + let user_id = req.param("id"); |
| 136 | + res.json({ id: user_id, name: "John Doe" }); |
| 137 | + }); |
| 138 | + |
| 139 | + // Start the server |
| 140 | + app.listen(3000, || { |
| 141 | + println("Server running on http://localhost:3000"); |
| 142 | + }); |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +## Advanced Patterns |
| 147 | + |
| 148 | +### Method Chaining |
| 149 | + |
| 150 | +Extern types can support method chaining by returning `self`: |
| 151 | + |
| 152 | +```husk |
| 153 | +extern mod builder { |
| 154 | + type QueryBuilder; |
| 155 | + |
| 156 | + impl QueryBuilder { |
| 157 | + fn select(self, columns: string) -> QueryBuilder; |
| 158 | + fn from(self, table: string) -> QueryBuilder; |
| 159 | + fn where_clause(self, condition: string) -> QueryBuilder; |
| 160 | + fn order_by(self, column: string) -> QueryBuilder; |
| 161 | + fn limit(self, count: int) -> QueryBuilder; |
| 162 | + fn build(self) -> string; |
| 163 | + } |
| 164 | +} |
| 165 | +
|
| 166 | +fn main() { |
| 167 | + let query = create_query() |
| 168 | + .select("name, email") |
| 169 | + .from("users") |
| 170 | + .where_clause("active = true") |
| 171 | + .order_by("created_at DESC") |
| 172 | + .limit(10) |
| 173 | + .build(); |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +### Mixed Static and Instance Methods |
| 178 | + |
| 179 | +Extern types can have both static methods (without `self`) and instance methods: |
| 180 | + |
| 181 | +```husk |
| 182 | +extern mod database { |
| 183 | + type Connection; |
| 184 | + |
| 185 | + // Static function to create a connection |
| 186 | + fn connect(url: string) -> Connection; |
| 187 | + |
| 188 | + impl Connection { |
| 189 | + // Instance methods with self |
| 190 | + fn query(self, sql: string) -> any; |
| 191 | + fn close(self); |
| 192 | + |
| 193 | + // Static methods without self |
| 194 | + fn escape_string(value: string) -> string; |
| 195 | + } |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +### Generic Types (Future Enhancement) |
| 200 | + |
| 201 | +While not yet implemented, future versions will support generic extern types: |
| 202 | + |
| 203 | +```husk |
| 204 | +extern mod promise { |
| 205 | + type Promise<T>; |
| 206 | + |
| 207 | + impl Promise<T> { |
| 208 | + fn then<U>(self, callback: fn(T) -> U) -> Promise<U>; |
| 209 | + fn catch(self, handler: fn(any) -> T) -> Promise<T>; |
| 210 | + } |
| 211 | +} |
| 212 | +``` |
| 213 | + |
| 214 | +## Type Safety |
| 215 | + |
| 216 | +Extern declarations maintain type safety at compile time: |
| 217 | + |
| 218 | +1. **Parameter Checking**: The compiler verifies that method calls have the correct number and types of arguments |
| 219 | +2. **Return Type Validation**: Return types are checked when extern functions are used in expressions |
| 220 | +3. **Self Parameter**: The `self` parameter is automatically handled and doesn't count toward the argument count when calling methods |
| 221 | + |
| 222 | +## Limitations |
| 223 | + |
| 224 | +Current limitations of extern declarations: |
| 225 | + |
| 226 | +1. **No Runtime Implementation**: Extern functions must exist in the JavaScript environment |
| 227 | +2. **No Generic Types**: Generic parameters are not yet supported |
| 228 | +3. **Limited Type Mapping**: Complex JavaScript types may need to be represented as `any` |
| 229 | +4. **No Property Access**: Direct property access on extern types is not supported; use methods instead |
| 230 | + |
| 231 | +## Best Practices |
| 232 | + |
| 233 | +1. **Group Related Functionality**: Use extern modules to organize related types and functions |
| 234 | +2. **Document External Dependencies**: Clearly document which npm packages or browser APIs are required |
| 235 | +3. **Type Safety First**: Prefer specific types over `any` when possible |
| 236 | +4. **Method Naming**: Follow Husk naming conventions (snake_case) even for JavaScript APIs |
| 237 | +5. **Error Handling**: Consider wrapping extern calls that might throw in Result types |
| 238 | + |
| 239 | +## Integration with Build System |
| 240 | + |
| 241 | +When using extern declarations, ensure your `husk.toml` includes the necessary dependencies: |
| 242 | + |
| 243 | +```toml |
| 244 | +[dependencies] |
| 245 | +express = "^5.0.0" |
| 246 | + |
| 247 | +[build] |
| 248 | +target = "node-esm" |
| 249 | +``` |
| 250 | + |
| 251 | +The build system will: |
| 252 | +- Generate appropriate import statements in the transpiled JavaScript |
| 253 | +- Include type definitions for better IDE support |
| 254 | +- Ensure dependencies are available at runtime |
| 255 | + |
| 256 | +## Future Enhancements |
| 257 | + |
| 258 | +Planned improvements to extern declarations include: |
| 259 | + |
| 260 | +1. **Generic Type Parameters**: Support for `Promise<T>`, `Array<T>`, etc. |
| 261 | +2. **Property Declarations**: Direct property access syntax |
| 262 | +3. **TypeScript Integration**: Automatic generation from `.d.ts` files |
| 263 | +4. **Async/Await**: Native support for Promise-based APIs |
| 264 | +5. **Operator Overloading**: Support for JavaScript operators on extern types |
0 commit comments