Summary
Web/src/docs/types/slices.md:109-119 shows an FFI example using memcpy:
extern func memcpy(dst: *opaque, src: *opaque, n: uint) -> *opaque;
let src = [1, 2, 3, 4];
let dst: uint8[4];
memcpy(dst.data, src.data, src.length);
The example is broken on at least three independent points, plus a fourth that surfaces the moment a user tries to fix the others. None of these problems are addressed by the surrounding text or by extern.md.
The four defects
extern declaration has no library binding
memcpy lives in msvcrt.dll on Windows / libc on POSIX. The doc's bare extern func memcpy(...) declaration does not tell the linker where to find the symbol. On Windows the build fails to resolve memcpy unless the user adds:
@[Import(lib: "msvcrt.dll")]
extern func memcpy(dst: *opaque, src: *opaque, n: uint) -> *opaque;
extern.md (the dedicated FFI doc) has no mention of @[Import] at all — it shows only bare extern declarations as if that's sufficient:
extern func malloc(size: uint) -> *opaque;
extern func free(ptr: *opaque);
extern func sin(angle: float) -> float;
The @[Import(lib: ...)] mechanism is used in Web/src/blog/language-without-llvm.md:127 and is presumably how stdlib modules like Std::Memory reach kernel32.dll / msvcrt.dll, but it's nowhere in the user-facing FFI documentation. Users have no documented path from "I want to call libc / Win32" to a working program.
let src = [1, 2, 3, 4] is int64[], not uint8[]
The doc later does memcpy(dst.data, src.data, src.length) where dst: uint8[4]. The two slices are not type-compatible — src's element type is int64 (8 bytes per element), dst's is uint8 (1 byte). Even through *opaque, the byte counts and layouts don't line up.
Hir.cpp:682 types unsuffixed integer literals as int (= int64 on x86-64). Nothing in the doc text alerts the reader that [1, 2, 3, 4] won't be uint8[] by default, or that suffixes like 1u8 are needed to match dst's element type.
let dst: uint8[4] doesn't compile
let requires an initializer (Sema.cpp:2060), so the bare declaration errors with "immutable variable requires an initializer." Already filed separately; mentioning here because it's part of why a user copy-pasting this example gets stuck before they can even reach defects 1 and 2.
src.length is the count of elements, not bytes
Even if a user fixes defects 1-3 (add @[Import], switch dst to a matching element type, change let to var), the call still produces wrong values because src.length == 4 regardless of element width. memcpy's third parameter is a byte count. For int64[4], the user needs src.length * sizeof(int64) = 32 bytes, not 4.
The user's actual failing case after fixing 1-3 (using int[4] for both): memcpy copies 4 bytes out of 32, leaving 28 bytes of dst uninitialized. Printed values like 4294967297 (= 0x100000001) are exactly what you'd expect: the first 4 bytes of the first int64 source element (01 00 00 00) land in the first 4 bytes of dst[0], with the upper 4 bytes inheriting whatever was on the stack. dst[1] through dst[3] are fully uninitialized stack garbage.
Nothing in the surrounding text mentions the byte/element distinction. This is a sharp foot-gun on top of the FFI/ergonomic ones.
What the example presumably should show
A working version of the same snippet:
@[Import(lib: "msvcrt.dll")]
extern func memcpy(dst: *opaque, src: *opaque, n: uint) -> *opaque;
let src = [1u8, 2u8, 3u8, 4u8]; // typed uint8 explicitly
var dst: uint8[4] = [0u8, 0u8, 0u8, 0u8]; // initialized; var because we'll write through .data
memcpy(dst.data, src.data, src.length * sizeof(uint8)); // bytes, not elements
(Or, if the goal is specifically to show calling memcpy on int64 data, leave [1, 2, 3, 4] and dst: int[4] and use src.length * sizeof(int) for the byte count.)
The point of the example is to illustrate slice.data as a *T that can be passed to extern functions — that lesson stands. The current snippet just buries it under three independent ways to be wrong.
Suggested fix
Two halves:
For slices.md:109-119 — rewrite the snippet so it actually works. Pick one of the working forms above. Add a sentence noting that slice.length is in elements, multiply by sizeof(T) for byte counts. Either fix the let dst → var dst issue here too, or wait for the broader doc sweep.
For extern.md — this is the bigger gap. The page should cover:
- How to bind an extern declaration to a specific library (
@[Import(lib: "msvcrt.dll")] and friends).
- What the default behavior is when no
@[Import] is given (does the compiler search system libs? error at link time? something else?).
- Per-platform examples for the common targets (Windows
kernel32/msvcrt, POSIX libc, etc.).
- Whether there's a way to declare
extern for non-DLL/SO inputs, e.g., static libraries or system call ABIs.
Right now the FFI doc is three lines of examples and one sentence about safety. A user who reads it has no idea how to actually link against anything.
Summary
Web/src/docs/types/slices.md:109-119shows an FFI example usingmemcpy:The example is broken on at least three independent points, plus a fourth that surfaces the moment a user tries to fix the others. None of these problems are addressed by the surrounding text or by
extern.md.The four defects
externdeclaration has no library bindingmemcpylives inmsvcrt.dllon Windows /libcon POSIX. The doc's bareextern func memcpy(...)declaration does not tell the linker where to find the symbol. On Windows the build fails to resolvememcpyunless the user adds:extern.md(the dedicated FFI doc) has no mention of@[Import]at all — it shows only bare extern declarations as if that's sufficient:The
@[Import(lib: ...)]mechanism is used inWeb/src/blog/language-without-llvm.md:127and is presumably how stdlib modules likeStd::Memoryreachkernel32.dll/msvcrt.dll, but it's nowhere in the user-facing FFI documentation. Users have no documented path from "I want to call libc / Win32" to a working program.let src = [1, 2, 3, 4]isint64[], notuint8[]The doc later does
memcpy(dst.data, src.data, src.length)wheredst: uint8[4]. The two slices are not type-compatible —src's element type isint64(8 bytes per element),dst's isuint8(1 byte). Even through*opaque, the byte counts and layouts don't line up.Hir.cpp:682types unsuffixed integer literals asint(=int64on x86-64). Nothing in the doc text alerts the reader that[1, 2, 3, 4]won't beuint8[]by default, or that suffixes like1u8are needed to matchdst's element type.let dst: uint8[4]doesn't compileletrequires an initializer (Sema.cpp:2060), so the bare declaration errors with "immutable variable requires an initializer." Already filed separately; mentioning here because it's part of why a user copy-pasting this example gets stuck before they can even reach defects 1 and 2.src.lengthis the count of elements, not bytesEven if a user fixes defects 1-3 (add
@[Import], switchdstto a matching element type, changelettovar), the call still produces wrong values becausesrc.length == 4regardless of element width.memcpy's third parameter is a byte count. Forint64[4], the user needssrc.length * sizeof(int64)= 32 bytes, not 4.The user's actual failing case after fixing 1-3 (using
int[4]for both):memcpycopies 4 bytes out of 32, leaving 28 bytes ofdstuninitialized. Printed values like4294967297(=0x100000001) are exactly what you'd expect: the first 4 bytes of the firstint64source element (01 00 00 00) land in the first 4 bytes ofdst[0], with the upper 4 bytes inheriting whatever was on the stack.dst[1]throughdst[3]are fully uninitialized stack garbage.Nothing in the surrounding text mentions the byte/element distinction. This is a sharp foot-gun on top of the FFI/ergonomic ones.
What the example presumably should show
A working version of the same snippet:
(Or, if the goal is specifically to show calling
memcpyonint64data, leave[1, 2, 3, 4]anddst: int[4]and usesrc.length * sizeof(int)for the byte count.)The point of the example is to illustrate
slice.dataas a*Tthat can be passed toexternfunctions — that lesson stands. The current snippet just buries it under three independent ways to be wrong.Suggested fix
Two halves:
For
slices.md:109-119— rewrite the snippet so it actually works. Pick one of the working forms above. Add a sentence noting thatslice.lengthis in elements, multiply bysizeof(T)for byte counts. Either fix thelet dst→var dstissue here too, or wait for the broader doc sweep.For
extern.md— this is the bigger gap. The page should cover:@[Import(lib: "msvcrt.dll")]and friends).@[Import]is given (does the compiler search system libs? error at link time? something else?).kernel32/msvcrt, POSIXlibc, etc.).externfor non-DLL/SO inputs, e.g., static libraries or system call ABIs.Right now the FFI doc is three lines of examples and one sentence about safety. A user who reads it has no idea how to actually link against anything.