Skip to main content

Releasing v0.4.0 — LSP goto-definition, stdlib fixes, and WASM coverage

· 5 min read
v0.4.0

v0.4.0 was cut on 2026-04-15. This post is the announcement, written a couple of weeks later.

The release is smaller than 0.3.0 in scope — no new syntax, no new type system features — but it ships six breaking changes alongside LSP, documentation, and stdlib improvements.

Breaking changes

Six breaking changes shipped in this release. Three are compile-time-blocked (the compiler will refuse to build until they are addressed). Three are silent behaviour changes that require a manual audit.

stdlib public API uses int uniformly. i32 and i64 have been removed from the public signatures of 22 stdlib modules (channel, fs, net/http, net/http_client, net/smtp, net/tls, net/websocket, net/quic, net/net, encoding/json, encoding/toml, encoding/yaml, encoding/csv, encoding/xml, encoding/protobuf, encoding/msgpack, encoding/wire, net/ipnet, path, semaphore, text/semver, time/cron). int is the correct type for all of them. Find binding declarations and call-argument positions where you passed i32/i64 against a stdlib signature, and change them to int. ABI seams inside extern "C" blocks or annotated // INTERNAL-ABI: are exempt. (#1218) (Note: int itself was removed as a type alias in v0.5 — the stdlib reverted those signatures to explicit i64 widths. If migrating from v0.4, skip this step and go straight to the v0.5 migration guide.)

Explicit teardown for http.Server and regex.Pattern. The impl Drop registration for both types was removed to eliminate a double-free: the drop was calling the same underlying free function as the explicit method, so any code that called server.close() or pat.free() before scope exit would double-free the allocation. The implicit scope-exit release is gone. Add server.close() at every site where an http.Server exits scope, and pat.free() at every site where a regex.Pattern exits scope. (#1314)

Explicit teardown for http.Request and json.Value. Same change as above, same rationale. Add req.free() before every HTTP request handler returns, and val.free() for every json.Value — including intermediate values produced by val.get_field(...). (#1500)

gzip_decompress, deflate_decompress, and zlib_decompress require max_output_len. These functions now take a required second argument max_output_len: int. The change closes a gzip-bomb path: the previous implementation read to end with no bound, so a crafted payload could cause OOM rather than a clean error. Add the argument at each call site; if no tighter bound applies, use 64 * 1024 * 1024 (64 MiB). This is compile-time-blocked. (#1471)

http_client string helpers return Option<String>. request_string, get_string, and post_string now return Option<String> instead of String. None is returned on transport failure. Match or if let at each call site. This is compile-time-blocked. (#1030)

WASM empty-result encoding uses canonical JSON literals. Optional scalar exports (hover, goto_definition, find_references, prepare_rename, signature_help) now return the string "null" when there is no result, replacing the previous empty string "". JSON.parse("") throws a SyntaxError; JSON.parse("null") parses cleanly. Update the empty-result guard in any JavaScript or TypeScript integration from result === "" to result === "null". Collection exports (rename, inlay_hints) already returned "[]" and are unchanged. (#1506)

For detailed before/after code shapes and failure modes for each change, see the v0.4.0 migration guide.

Also in this release

LSP transitive goto-definition. find_cross_file_definition now follows one import hop with a cycle guard. Previously, goto-definition worked only for symbols defined directly in the current file or its immediate imports; re-exports and glob chains stopped it cold. Now, if the symbol resolves through a re-export or a glob, the LSP follows one hop and lands on the actual definition site. The cycle guard prevents infinite traversal across circular import graphs. (#1073)

Module search-path documentation. The rules for how import resolves module paths have been documented end-to-end: HEWPATH, HEW_STD, the four-step resolution order, and the role (and deliberate non-role) of hew.toml. Previously this was only partially described in scattered doc comments. The documentation is now in the user-facing module discovery docs and accurately describes what the resolver actually does. (#1074)

Eval WASM and --json ok-path coverage. Integration tests now exercise hew eval --json --target wasm32-wasi on the success path. The WASM capability matrix document has been updated with the non-interactive eval contract, clarifying what hew eval can and cannot do under wasm32-wasi. (#1075)

stdlib URL percent-encoding. url.encode, url.decode, and url.encode_query are now confirmed end-to-end on both native and WASM. A specific fix here is that hew-runtime now exports a bounded hew_bytes_to_string helper, which allows url.decode to work correctly under wasm32-wasip1 — previously the WASM path silently failed when a decoded byte sequence crossed the boundary. (#1077)

fs.try_read_bytes binary safety. try_read_bytes now calls hew_file_read_bytes directly with proper hew_file_last_error handling, instead of routing through the UTF-8 string path. Files containing non-UTF-8 bytes or embedded NUL bytes now round-trip correctly. Previously, reading a binary file through try_read_bytes would either corrupt the result or return an error when the content was valid binary but not valid UTF-8. (#1076)

Hew Notes

Language development, engineering articles, releases and community projects. About once a month, with an extra issue for a significant release. Written in English.

Subscribe to Hew Notes