`read!` and scalar variables

Hi,

Is there a specific reason why read! does not accept scalars ? The signature allows for the AbstractArray type, but not scalars.

So essentially, this works:

ios = open("./proprietary.format")
ver = Array{UInt32}(undef,1)
read!(ios,ver)

While this does not:

ios = open("./proprietary.format")
ver = Array{UInt32}(undef,0)
read!(ios,ver)

I’m very curious about what the reasoning behind this is. It will probably also help me write safer code.

Thanks!

read! is the mutating version of read, the ! bang indicating that it changes the contents of its argument. For AbstractArray types, this makes sense, as an array passed into the function can have its contents modified by the function.

The scalar types that Julia has support for reading from files don’t have such mutable internals i.e. you can’t pass an x::Int to a function and have x’s value modified by the function after the call. So the scalar types Int, Float64, Complex, etc. have read(io, Type) methods rather than mutating read! methods.

This is a zero-length array that cannot store any content. Perhaps you intended to create a zero-dimensional array instead? For those, read! does work:

julia> ver = Array{UInt32, 0}(undef)
0-dimensional Array{UInt32, 0}:
0x00000000

julia> read!(ios, ver)
0-dimensional Array{UInt32, 0}:
0x68542023
2 Likes

That was very informative, thank you! I have a small follow-up: are these zero-dimensional arrays equivalent to scalars?

EDIT: Also, how does one initialise a scalar in a manner analogous to x = Array{Int32,1}(undef,5) or x = Array{UInt32,0}(undef) ?

Well, in the sense that they only contain a single instance in them, they are equivalent to us. But to Julia’s type system, they are distinct things. So a function that accepts a UInt32 won’t accept the Array{UInt32, 0} constructed above.

In practice, zero-dimensional arrays are rarely used, and scalars are much more common.

It’s usually not necessary, but you can use the local keyword. From the manual:

local x declares a new local variable in that scope, regardless of whether there is already a variable named x in an outer scope or not. Declaring each new variable like this is somewhat verbose and tedious, however, so Julia, like many other languages, considers assignment to a variable name that doesn’t already exist to implicitly declare that variable.

The normal way to create a scalar is to just assign to it, x = 5.0 creates a new Float64 variable x automatically (assuming x doesn’t already exist). Most Julia code initializes variables by giving them zero values, for eg. c = zero(Complex{Int}) initializes c as a Complex variable and gives it a 0 + 0im initial value.

local x or local x::Int (the second one declares x to hold only Int values) are useful only in specific scenarios to get around scoping issues. They initialize the variable but don’t give it any value, so you can’t read from the variables declared this way, only assign to them.

2 Likes

Thank you again for the thorough information. Really appreciated!