From 60946e03d2913efb7c829e2368409b11c90b0dc1 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sat, 23 Jan 2016 21:13:46 -0500 Subject: [PATCH] add unsafe_read(io, p::Ptr{UInt8}, nb::UInt) counterpart to unsafe_write --- base/exports.jl | 2 +- base/filesystem.jl | 13 ++---- base/io.jl | 91 +++++++++++++++++++++++---------------- base/iobuffer.jl | 34 +++++++-------- base/iostream.jl | 16 ++++--- base/stream.jl | 91 ++++++++++++++++++++++++++------------- doc/stdlib/io-network.rst | 16 +++++++ 7 files changed, 162 insertions(+), 101 deletions(-) diff --git a/base/exports.jl b/base/exports.jl index 7bd57ebd007990..d9c072d85a600c 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1332,7 +1332,7 @@ export unsafe_copy!, unsafe_load, unsafe_pointer_to_objref, - #unsafe_read, + unsafe_read, unsafe_store!, unsafe_write, diff --git a/base/filesystem.jl b/base/filesystem.jl index 2f8ddf111e9f79..1818bbf78df912 100644 --- a/base/filesystem.jl +++ b/base/filesystem.jl @@ -144,18 +144,13 @@ function read(f::File, ::Type{UInt8}) return ret % UInt8 end -function read!(f::File, a::Vector{UInt8}, nel=length(a)) +function unsafe_read(f::File, p::Ptr{UInt8}, nel::UInt) check_open(f) - if nel < 0 || nel > length(a) - throw(BoundsError()) - end ret = ccall(:jl_fs_read, Int32, (Int32, Ptr{Void}, Csize_t), - f.handle, a, nel) - if ret < nel - throw(EOFError()) - end + f.handle, p, nel) uv_error("read",ret) - return a + ret == nel || throw(EOFError()) + nothing end nb_available(f::File) = filesize(f) - position(f) diff --git a/base/io.jl b/base/io.jl index 4d95906c954220..4481d9e03345a6 100644 --- a/base/io.jl +++ b/base/io.jl @@ -26,14 +26,38 @@ function eof end read(s::IO, ::Type{UInt8}) = error(typeof(s)," does not support byte I/O") write(s::IO, x::UInt8) = error(typeof(s)," does not support byte I/O") +""" + unsafe_write(io, ref, nbytes) + +Copy nbytes from ref (converted to a pointer) into the IO stream object. + +It is recommended that IO subtypes override the exact method signature below +to provide more efficient implementations. +""" function unsafe_write(s::IO, p::Ptr{UInt8}, n::UInt) local written::Int = 0 - for i=1:n + for i = 1:n written += write(s, unsafe_load(p, i)) end return written end +""" + unsafe_read(io, ref, nbytes) + +Copy nbytes from the IO stream object into ref (converted to a pointer). + +It is recommended that IO subtypes override the exact method signature below +to provide more efficient implementations. +""" +function unsafe_read(s::IO, p::Ptr{UInt8}, n::UInt) + for i = 1:n + unsafe_store!(p, read(s, UInt8)::UInt8, i) + end + nothing +end + + # Generic wrappers around other IO objects abstract AbstractPipe <: IO function pipe_reader end @@ -45,11 +69,9 @@ buffer_writes(io::AbstractPipe, args...) = buffer_writes(pipe_writer(io), args.. flush(io::AbstractPipe) = flush(pipe_writer(io)) read(io::AbstractPipe, byte::Type{UInt8}) = read(pipe_reader(io), byte) -read!(io::AbstractPipe, bytes::Vector{UInt8}) = read!(pipe_reader(io), bytes) -read{T<:AbstractPipe}(io::T, args...) = read(pipe_reader(io), args...) -read!{T<:AbstractPipe}(io::T, args...) = read!(pipe_reader(io), args...) -readuntil{T<:AbstractPipe}(io::T, args...) = readuntil(pipe_reader(io), args...) +unsafe_read(io::AbstractPipe, p::Ptr{UInt8}, nb::UInt) = unsafe_read(pipe_reader(io), p, nb) read(io::AbstractPipe) = read(pipe_reader(io)) +readuntil{T<:AbstractPipe}(io::T, args...) = readuntil(pipe_reader(io), args...) readavailable(io::AbstractPipe) = readavailable(pipe_reader(io)) isreadable(io::AbstractPipe) = isreadable(pipe_reader(io)) @@ -107,11 +129,11 @@ function write(io::IO, xs...) return written end -unsafe_write{T}(s::IO, p::Ref{T}, n::Integer) = unsafe_write(s, unsafe_convert(Ref{T}, p)::Ptr, n) +@noinline unsafe_write{T}(s::IO, p::Ref{T}, n::Integer) = unsafe_write(s, unsafe_convert(Ref{T}, p)::Ptr, n) # mark noinline to ensure ref is gc-rooted somewhere (by the caller) unsafe_write(s::IO, p::Ptr, n::Integer) = unsafe_write(s, convert(Ptr{UInt8}, p), convert(UInt, n)) -write(s::IO, x::Ref) = unsafe_write(s, x, sizeof(eltype(x))) - -function write(s::IO, x::Union{Int8,Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128,Float16,Float32,Float64}) +write{T}(s::IO, x::Ref{T}) = unsafe_write(s, x, Core.sizeof(T)) +write(s::IO, x::Int8) = write(s, reinterpret(UInt8, x)) +function write(s::IO, x::Union{Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128,Float16,Float32,Float64}) return write(s, Ref(x)) end @@ -126,16 +148,20 @@ function write(s::IO, a::AbstractArray) return nb end -function write{T}(s::IO, a::Array{T}) +@noinline function write(s::IO, a::Array{UInt8}) # mark noinline to ensure the array is gc-rooted somewhere (by the caller) + return unsafe_write(s, pointer(a), sizeof(a)) +end + +@noinline function write{T}(s::IO, a::Array{T}) # mark noinline to ensure the array is gc-rooted somewhere (by the caller) if isbits(T) return unsafe_write(s, pointer(a), sizeof(a)) + else + nb = 0 + for i in eachindex(a) + nb += write(s, a[i]) + end + return nb end - - nb = 0 - for x in a - nb += write(s, x) - end - return nb end @@ -171,23 +197,17 @@ function write(to::IO, from::IO) end end +@noinline unsafe_read{T}(s::IO, p::Ref{T}, n::Integer) = unsafe_read(s, unsafe_convert(Ref{T}, p)::Ptr, n) # mark noinline to ensure ref is gc-rooted somewhere (by the caller) +unsafe_read(s::IO, p::Ptr, n::Integer) = unsafe_read(s, convert(Ptr{UInt8}, p), convert(UInt, n)) +read{T}(s::IO, x::Ref{T}) = (unsafe_read(s, x, Core.sizeof(T)); x) -read(s::IO, ::Type{Int8}) = reinterpret(Int8, read(s,UInt8)) - -function read{T <: Union{Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128}}(s::IO, ::Type{T}) - x = zero(T) - for n = 1:sizeof(x) - x |= (convert(T,read(s,UInt8))<<((n-1)<<3)) - end - return x +read(s::IO, ::Type{Int8}) = reinterpret(Int8, read(s, UInt8)) +function read(s::IO, T::Union{Type{Int16},Type{UInt16},Type{Int32},Type{UInt32},Type{Int64},Type{UInt64},Type{Int128},Type{UInt128},Type{Float16},Type{Float32},Type{Float64}}) + return read(s, Ref{T}(0))[]::T end read(s::IO, ::Type{Bool}) = (read(s,UInt8)!=0) -read(s::IO, ::Type{Float16}) = box(Float16,unbox(Int16,read(s,Int16))) -read(s::IO, ::Type{Float32}) = box(Float32,unbox(Int32,read(s,Int32))) -read(s::IO, ::Type{Float64}) = box(Float64,unbox(Int64,read(s,Int64))) - -read{T}(s::IO, ::Type{Ptr{T}}) = convert(Ptr{T}, read(s,UInt)) +read{T}(s::IO, ::Type{Ptr{T}}) = convert(Ptr{T}, read(s, UInt)) read{T}(s::IO, t::Type{T}, d1::Int, dims::Int...) = read(s, t, tuple(d1,dims...)) read{T}(s::IO, t::Type{T}, d1::Integer, dims::Integer...) = @@ -195,17 +215,14 @@ read{T}(s::IO, t::Type{T}, d1::Integer, dims::Integer...) = read{T}(s::IO, ::Type{T}, dims::Dims) = read!(s, Array(T, dims)) -function read!(s::IO, a::Vector{UInt8}) - for i in 1:length(a) - a[i] = read(s, UInt8) - end +@noinline function read!(s::IO, a::Array{UInt8}) # mark noinline to ensure the array is gc-rooted somewhere (by the caller) + unsafe_read(s, pointer(a), sizeof(a)) return a end -function read!{T}(s::IO, a::Array{T}) +@noinline function read!{T}(s::IO, a::Array{T}) # mark noinline to ensure the array is gc-rooted somewhere (by the caller) if isbits(T) - nb::Int = length(a) * sizeof(T) - read!(s, reinterpret(UInt8, a, (nb,))) + unsafe_read(s, pointer(a), sizeof(a)) else for i in eachindex(a) a[i] = read(s, T) @@ -324,7 +341,7 @@ function read(s::IO, nb=typemax(Int)) # instead of taking of risk of over-allocating b = Array(UInt8, nb == typemax(Int) ? 1024 : nb) nr = readbytes!(s, b, nb) - resize!(b, nr) + return resize!(b, nr) end function readstring(s::IO) diff --git a/base/iobuffer.jl b/base/iobuffer.jl index cd1d3307348147..9395e0bd0ff833 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -39,7 +39,7 @@ function copy(b::AbstractIOBuffer) b.readable, b.writable, b.seekable, b.append, b.maxsize) ret.size = b.size ret.ptr = b.ptr - ret + return ret end show(io::IO, b::AbstractIOBuffer) = print(io, "IOBuffer(data=UInt8[...], ", @@ -52,8 +52,17 @@ show(io::IO, b::AbstractIOBuffer) = print(io, "IOBuffer(data=UInt8[...], ", "ptr=", b.ptr, ", ", "mark=", b.mark, ")") -read!(from::AbstractIOBuffer, a::Vector{UInt8}) = read_sub(from, a, 1, length(a)) -read!(from::AbstractIOBuffer, a::Array) = read_sub(from, a, 1, length(a)) +function unsafe_read(from::AbstractIOBuffer, p::Ptr{UInt8}, nb::UInt) + from.readable || throw(ArgumentError("read failed, IOBuffer is not readable")) + avail = nb_available(from) + adv = min(avail, nb) + unsafe_copy!(p, pointer(from.data, from.ptr), adv) + from.ptr += adv + if nb > avail + throw(EOFError()) + end + nothing +end function read_sub{T}(from::AbstractIOBuffer, a::AbstractArray{T}, offs, nel) from.readable || throw(ArgumentError("read failed, IOBuffer is not readable")) @@ -61,18 +70,8 @@ function read_sub{T}(from::AbstractIOBuffer, a::AbstractArray{T}, offs, nel) throw(BoundsError()) end if isbits(T) && isa(a,Array) - nb = nel * sizeof(T) - avail = nb_available(from) - adv = min(avail, nb) - copy!(pointer_to_array(convert(Ptr{UInt8},pointer(a)), sizeof(a)), # reinterpret(UInt8,a) but without setting the shared data property on a - 1 + (1 - offs) * sizeof(T), - from.data, - from.ptr, - adv) - from.ptr += adv - if nb > avail - throw(EOFError()) - end + nb = UInt(nel * sizeof(T)) + unsafe_read(from, pointer(a, offs), nb) else for i = offs:offs+nel-1 a[i] = read(to, T) @@ -82,9 +81,9 @@ function read_sub{T}(from::AbstractIOBuffer, a::AbstractArray{T}, offs, nel) end @inline function read(from::AbstractIOBuffer, ::Type{UInt8}) + from.readable || throw(ArgumentError("read failed, IOBuffer is not readable")) ptr = from.ptr size = from.size - from.readable || throw(ArgumentError("read failed, IOBuffer is not readable")) if ptr > size throw(EOFError()) end @@ -323,7 +322,8 @@ end return sizeof(UInt8) end -function readbytes!(io::AbstractIOBuffer, b::Array{UInt8}, nb=length(b)) +readbytes!(io::AbstractIOBuffer, b::Array{UInt8}, nb=length(b)) = readbytes!(io, b, Int(nb)) +function readbytes!(io::AbstractIOBuffer, b::Array{UInt8}, nb::Int) nr = min(nb, nb_available(io)) if length(b) < nr resize!(b, nr) diff --git a/base/iostream.jl b/base/iostream.jl index 411ae8cd604241..31cf086933789c 100644 --- a/base/iostream.jl +++ b/base/iostream.jl @@ -125,7 +125,7 @@ function unsafe_write(s::IOStream, p::Ptr{UInt8}, nb::UInt) if !iswritable(s) throw(ArgumentError("write failed, IOStream is not writeable")) end - Int(ccall(:ios_write, Csize_t, (Ptr{Void}, Ptr{Void}, Csize_t), s.ios, p, nb)) + return Int(ccall(:ios_write, Csize_t, (Ptr{Void}, Ptr{Void}, Csize_t), s.ios, p, nb)) end function write{T,N,A<:Array}(s::IOStream, a::SubArray{T,N,A}) @@ -153,19 +153,21 @@ function read(s::IOStream, ::Type{UInt8}) if b == -1 throw(EOFError()) end - b % UInt8 + return b % UInt8 end -function read{T<:Union{UInt16, Int16, UInt32, Int32, UInt64, Int64}}(s::IOStream, ::Type{T}) - ccall(:jl_ios_get_nbyte_int, UInt64, (Ptr{Void}, Csize_t), s.ios, sizeof(T)) % T +if ENDIAN_BOM == 0x04030201 +function read(s::IOStream, T::Union{Type{Int16},Type{UInt16},Type{Int32},Type{UInt32},Type{Int64},Type{UInt64}}) + return ccall(:jl_ios_get_nbyte_int, UInt64, (Ptr{Void}, Csize_t), s.ios, sizeof(T)) % T +end end -function read!(s::IOStream, a::Vector{UInt8}) +function unsafe_read(s::IOStream, p::Ptr{UInt8}, nb::UInt) if ccall(:ios_readall, Csize_t, - (Ptr{Void}, Ptr{Void}, Csize_t), s.ios, a, sizeof(a)) < sizeof(a) + (Ptr{Void}, Ptr{Void}, Csize_t), s, p, nb) != nb throw(EOFError()) end - a + nothing end ## text I/O ## diff --git a/base/stream.jl b/base/stream.jl index ce8838bb758c64..236dbf3c10f0d5 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -664,7 +664,7 @@ type Timer ccall(:uv_timer_start, Cint, (Ptr{Void},Ptr{Void},UInt64,UInt64), this.handle, uv_jl_timercb::Ptr{Void}, UInt64(round(timeout*1000))+1, UInt64(round(repeat*1000))) - this + return this end end @@ -678,6 +678,7 @@ function close(t::Timer) ccall(:uv_timer_stop, Cint, (Ptr{Void},), t.handle) ccall(:jl_close_uv, Void, (Ptr{Void},), t.handle) end + nothing end function _uv_hook_close(t::Timer) @@ -723,7 +724,7 @@ function Timer(cb::Function, timeout::Real, repeat::Real=0.0) # we re-enter the event loop. this avoids a race condition. see issue #12719 enq_work(current_task()) yieldto(waiter) - t + return t end ## event loop ## @@ -736,9 +737,9 @@ end function process_events(block::Bool) loop = eventloop() if block - ccall(:jl_run_once,Int32,(Ptr{Void},),loop) + return ccall(:jl_run_once,Int32,(Ptr{Void},),loop) else - ccall(:jl_process_events,Int32,(Ptr{Void},),loop) + return ccall(:jl_process_events,Int32,(Ptr{Void},),loop) end end @@ -758,7 +759,7 @@ function init_pipe!(pipe::LibuvPipe; (Ptr{Void}, Int32, Int32, Int32), pipe.handle, writable, readable, julia_only)) pipe.status = StatusInit - pipe + return pipe end function malloc_julia_pipe!(x::LibuvPipe) @@ -766,11 +767,13 @@ function malloc_julia_pipe!(x::LibuvPipe) x.handle = Libc.malloc(_sizeof_uv_named_pipe) associate_julia_struct(x.handle, x) finalizer(x, uvfinalize) + nothing end function _link_pipe(read_end::Ptr{Void}, write_end::Ptr{Void}) uv_error("pipe_link", ccall(:uv_pipe_link, Int32, (Ptr{Void}, Ptr{Void}), read_end, write_end)) + nothing end function link_pipe(read_end::Ptr{Void}, readable_julia_only::Bool, @@ -783,6 +786,7 @@ function link_pipe(read_end::Ptr{Void}, readable_julia_only::Bool, uv_error("init_pipe(write)", ccall(:jl_init_pipe, Cint, (Ptr{Void},Int32,Int32,Int32), write_end, 1, 0, writable_julia_only)) _link_pipe(read_end, write_end) + nothing end function link_pipe(read_end::Ptr{Void}, readable_julia_only::Bool, @@ -792,6 +796,7 @@ function link_pipe(read_end::Ptr{Void}, readable_julia_only::Bool, uv_error("init_pipe(write)", ccall(:jl_init_pipe, Cint, (Ptr{Void},Int32,Int32,Int32), write_end, 1, 0, writable_julia_only)) _link_pipe(read_end,write_end) + nothing end function link_pipe(read_end::PipeEndpoint, readable_julia_only::Bool, @@ -805,6 +810,7 @@ function link_pipe(read_end::PipeEndpoint, readable_julia_only::Bool, ccall(:jl_init_pipe, Cint, (Ptr{Void},Int32,Int32,Int32), write_end, 1, 0, writable_julia_only)) _link_pipe(read_end.handle, write_end) read_end.status = StatusOpen + nothing end function link_pipe(read_end::Ptr{Void}, readable_julia_only::Bool, @@ -818,6 +824,7 @@ function link_pipe(read_end::Ptr{Void}, readable_julia_only::Bool, readable = false, writable = true, julia_only = writable_julia_only) _link_pipe(read_end, write_end.handle) write_end.status = StatusOpen + nothing end function link_pipe(read_end::PipeEndpoint, readable_julia_only::Bool, @@ -845,7 +852,7 @@ function close_pipe_sync(p::PipeEndpoint) end function close_pipe_sync(handle::Ptr{Void}) - ccall(:uv_pipe_close_sync, Void, (Ptr{Void},), handle) + return ccall(:uv_pipe_close_sync, Void, (Ptr{Void},), handle) end ## Functions for any LibuvStream ## @@ -858,11 +865,11 @@ function start_reading(stream::LibuvStream) ret = ccall(:uv_read_start, Cint, (Ptr{Void}, Ptr{Void}, Ptr{Void}), stream, uv_jl_alloc_buf::Ptr{Void}, uv_jl_readcb::Ptr{Void}) stream.status = StatusActive - ret + return ret elseif stream.status == StatusActive - Int32(0) + return Int32(0) else - Int32(-1) + return Int32(-1) end end @@ -886,21 +893,45 @@ function stop_reading(stream::LibuvStream) if stream.status == StatusActive ret = ccall(:uv_read_stop, Cint, (Ptr{Void},), stream) stream.status = StatusOpen - ret + return ret elseif stream.status == StatusOpen - Int32(0) + return Int32(0) else - Int32(-1) + return Int32(-1) end end -function read!(s::LibuvStream, b::Vector{UInt8}) - nb = length(b) - r = readbytes!(s, b, nb) - if r < nb - throw(EOFError()) +readbytes!(s::LibuvStream, a::Vector{UInt8}, nb = length(a)) = readbytes!(s, a, Int(nb)) +function readbytes!(s::LibuvStream, a::Vector{UInt8}, nb::Int) + sbuf = s.buffer + @assert sbuf.seekable == false + @assert sbuf.maxsize >= nb + + if nb_available(sbuf) >= nb + return readbytes!(sbuf, a, nb) + end + + if nb <= SZ_UNBUFFERED_IO # Under this limit we are OK with copying the array from the stream's buffer + wait_readnb(s, nb) + return readbytes!(sbuf, a, nb) + else + try + stop_reading(s) # Just playing it safe, since we are going to switch buffers. + newbuf = PipeBuffer(a, #=maxsize=# nb) + newbuf.size = 0 # reset the write pointer to the beginning + s.buffer = newbuf + write(newbuf, sbuf) + wait_readnb(s, Int(nb)) + compact(newbuf) + return nb_available(newbuf) + finally + s.buffer = sbuf + if !isempty(s.readnotify.waitq) + start_reading(s) # resume reading iff there are currently other read clients of the stream + end + end end - return b + @assert false # unreachable end function read(stream::LibuvStream) @@ -908,27 +939,27 @@ function read(stream::LibuvStream) return takebuf_array(stream.buffer) end -function readbytes!(s::LibuvStream, a::Vector{UInt8}, nb = length(a)) +function unsafe_read(s::LibuvStream, p::Ptr{UInt8}, nb::UInt) sbuf = s.buffer @assert sbuf.seekable == false @assert sbuf.maxsize >= nb if nb_available(sbuf) >= nb - return readbytes!(sbuf, a, nb) + return unsafe_read(sbuf, p, nb) end if nb <= SZ_UNBUFFERED_IO # Under this limit we are OK with copying the array from the stream's buffer - wait_readnb(s, nb) - r = readbytes!(sbuf, a, nb) + wait_readnb(s, Int(nb)) + unsafe_read(sbuf, p, nb) else try stop_reading(s) # Just playing it safe, since we are going to switch buffers. - newbuf = PipeBuffer(a, #=maxsize=# nb) + newbuf = PipeBuffer(pointer_to_array(p, nb), #=maxsize=# Int(nb)) newbuf.size = 0 # reset the write pointer to the beginning s.buffer = newbuf write(newbuf, sbuf) - wait_readnb(s, nb) - r = nb_available(newbuf) + wait_readnb(s, Int(nb)) + nb == nb_available(newbuf) || throw(EOFError()) finally s.buffer = sbuf if !isempty(s.readnotify.waitq) @@ -936,28 +967,28 @@ function readbytes!(s::LibuvStream, a::Vector{UInt8}, nb = length(a)) end end end - return r + nothing end function read(this::LibuvStream, ::Type{UInt8}) wait_readnb(this, 1) buf = this.buffer @assert buf.seekable == false - read(buf, UInt8) + return read(buf, UInt8) end function readavailable(this::LibuvStream) wait_readnb(this, 1) buf = this.buffer @assert buf.seekable == false - takebuf_array(buf) + return takebuf_array(buf) end function readuntil(this::LibuvStream, c::UInt8) wait_readbyte(this, c) buf = this.buffer @assert buf.seekable == false - readuntil(buf, c) + return readuntil(buf, c) end uv_write(s::LibuvStream, p::Vector{UInt8}) = uv_write(s, pointer(p), UInt(sizeof(p))) @@ -1192,7 +1223,7 @@ end isopen(s::BufferStream) = s.is_open close(s::BufferStream) = (s.is_open = false; notify(s.r_c; all=true); notify(s.close_c; all=true); nothing) read(s::BufferStream, ::Type{UInt8}) = (wait_readnb(s, 1); read(s.buffer, UInt8)) -read!(s::BufferStream, a::Vector{UInt8}) = (wait_readnb(s, length(a)); read!(s.buffer, a)) +unsafe_read(s::BufferStream, a::Ptr{UInt8}, nb::UInt) = (wait_readnb(s, Int(nb)); unsafe_read(s.buffer, a, nb)) nb_available(s::BufferStream) = nb_available(s.buffer) isreadable(s::BufferStream) = s.buffer.readable diff --git a/doc/stdlib/io-network.rst b/doc/stdlib/io-network.rst index e285c9fad78caf..08a15fa3614bbd 100644 --- a/doc/stdlib/io-network.rst +++ b/doc/stdlib/io-network.rst @@ -168,6 +168,22 @@ General I/O If ``all`` is ``true`` (the default), this function will block repeatedly trying to read all requested bytes, until an error or end-of-file occurs. If ``all`` is ``false``\ , at most one ``read`` call is performed, and the amount of data returned is device-dependent. Note that not all stream types support the ``all`` option. +.. function:: unsafe_read(io, ref, nbytes) + + .. Docstring generated from Julia source + + Copy nbytes from the IO stream object into ref (converted to a pointer). + + It is recommended that IO subtypes override the exact method signature below to provide more efficient implementations. + +.. function:: unsafe_write(io, ref, nbytes) + + .. Docstring generated from Julia source + + Copy nbytes from ref (converted to a pointer) into the IO stream object. + + It is recommended that IO subtypes override the exact method signature below to provide more efficient implementations. + .. function:: position(s) .. Docstring generated from Julia source