Objectives
Tell a borrowed inspect, an explicit copy, a direct output, a frozen resource binary, and a retained term apart by what they return.
Why
"Zero-copy" is a claim about a measured pointer, not a slogan. This library only says "no payload copy" where a test counted bytes or compared pointers inside the callback.
Model
borrow_stable inspect twice, return size and 1 if data pointers match
copy_binary new bytes, return the copy and the count
fill_output enif_make_new_binary, write 7s, no earlier payload buffer
blob_poke write the calloc buffer, token stays
blob_freeze resource binary points at that buffer
retain_binary enif_make_copy into a private env
borrow_len non-binary is an error, not a conversionTheory
A sub-binary (binary_part) is still a binary. copy_binary returns those bytes and the byte size. The parent binary's extra bytes are not part of the copy. The test uses a 100-byte prefix and a 3-byte slice.
<<1::7>> is not a binary. Both copy_binary and borrow_len return {:error, :not_binary}.
Freeze while a borrow count is outstanding returns {:error, :borrowed}. After blob_release, freeze returns {:ok, <<1, 2, 3>>}. blob_close does not invalidate that binary, because enif_make_resource_binary holds the resource.
The token from blob_token is an integer allocated at creation, not a pointer. Elixir never sees the address. Two pokes leave the token unchanged, which is the reuse evidence.
Lab
scripts/demo-cpu.sh
mix test test/dsl_test.exs test/own_test.exs
Expected
The demo prints copy {:ok, <<1, 2, 3>>, 3} and borrow {:ok, 32, 1}. borrow_len([1, 2]) fails the DSL test if it ever returns a binary.
Code
native/term.mojo for the three conversion modes. native/own.mojo blob_freeze and retain_binary. native/generated/bytes.mojo for the strict borrow.
Failure
Publishing a mutable buffer as a resource binary and then poking it would change a BEAM binary. Poke checks frozen and returns {:error, :not_mutable}. The test asserts that.
Exercise
Why can borrow_stable's third element be 1 without the function returning a pointer?
Answer
The comparison happens in the callback, and the result is an integer. The pointer dies with the ErlNifBinary descriptor, which is freed before the return.