Write bytes of object to io

I can read a struct from its bytes from an IO like so:

julia> struct MyInt; data::Int; end

julia> io = IOBuffer(); write(io, 42); seekstart(io)
IOBuffer(data=UInt8[...], readable=true, writable=true, seekable=true, append=false, size=8, maxsize=Inf, ptr=1, mark=-1)

julia> read!(io, Ref(MyInt(-999)))

How to do the inverse, e.g. write the bytes of a struct to an io? The obvious thing fails:

julia> write(io, MyInt(42))
ERROR: MethodError: no method matching write(::Base.GenericIOBuffer{Array{UInt8,1}}, ::MyInt)
Closest candidates are:
  write(::IO, ::Any) at io.jl:498
  write(::IO, ::Any, ::Any...) at io.jl:500
  write(::IO, ::Complex) at complex.jl:217
 [1] write(::Base.GenericIOBuffer{Array{UInt8,1}}, ::MyInt) at ./io.jl:498
 [2] top-level scope at none:0

Also I am confused by the error. It seems that the first listed signature write(::IO, ::Any) at io.jl:498 applies?

One way to do this is to reinterpret the struct as bytes (i.e. UInt8) and write those:

julia> write(io, reinterpret(UInt8, [MyInt(42)]))

I suspect this will be fairly inefficient, though, as allocating the Vector{MyInt} seems wasteful. Perhaps there is a better way.

Edit: See @yuyichao’s better solution below.

julia> write(io, Ref(MyInt(-999)))

write(io, Ref(MyInt(42))) works. (Under the hood, it calls unsafe_write to output the raw bytes of a struct.)

(However, realize that this kind of binary I/O does not result in portable files in general, because of endian-ness, sizeof(Int) difference between 32-bit and 64-bit machines, and potentially other factors.)


Can you comment, why I have to wrap my type with a Ref, while some other types like Int do not need to be wrapped?

You can wrap them too. There’s no harm to allow no wrapping when there isn’t ambiguity so the few builtin types allows this. In general your type may want a different behavior so that’s not there by default. No one stops you from implementing it for your type though if that’s the only way your type should be written.