I’m happy to announce the first release of vec
, a library for safe dynamic arrays with Rust-like mutability permissions.
You can find the package on opam here, and the source repository is here.
Looking for feedback and suggestions!
I’m happy to announce the first release of vec
, a library for safe dynamic arrays with Rust-like mutability permissions.
You can find the package on opam here, and the source repository is here.
Looking for feedback and suggestions!
I’m curious how you would compare this with Containers.Vector?
Oh, I didn’t know that existed.
Well, with vec
you can control the vector’s growth rate (though I imagine this is a very niche feature).
Also, from what I can see CCVector
s can only be read-write or read-only, vec
also supports write-only vectors. (Which is useful if e.g. you want to pass a buffer to some function to fill it, but don’t want the function to read its current data)
Super nitpicky here, but rust doesn’t have write-only either
Cool. I just worry that the Obj
games needed for code like this are tricky, and so the collective review and testing that comes with focusing on fewer different libraries versus having a wide variety is very valuable. For example, be very careful with Obj.magic 0
, it is an express elevator to segfault city. I don’t understand how one can safely avoid code like what CCVector does in fill_with_junk_
here in order to handle both flat float and general arrays safely. (And a drive-by comment, why is storing capacity necessary, isn’t it always possible to use the length of the backing array?)
I’m still wrapping my head around co- and contravariance.
Why is it important to define the main type
type ('a, -'p) t = (* ... *)
?
That is, why is the phantom type defined to be contravariant here?
From the type system point of view [R | W]
means read or write, but in the context of this library it is used to mean read and write. So you have to interpret a disjunction as a conjunction, hence the contravariance annotation. This way you change the least supertype (disjunction) to the greatest subtype (conjunction).
True, it’s tricky to ensure you’re using it safely (case in point: I actually forgot to handle the float array case ), but there’s no other way to create an “uninitialized” array.
Regarding capacity, reading it from the vector’s length
means jumping through an extra pointer, and I thought it’s better to keep it inside the Vec.t
record for better cache usage, given that it’s often checked together with other fields of the record (growth_rate
and length
).
Basically what @kantian said.
It’s so that you can pass a ('a, [R | W]) Vec.t
to a function expecting a ('a, [> R]) Vec.t
.
Pvec
from @dbuenzli (unreleased) wants to provide a vector without the required value to initiate it (and without Obj.magic
). I don’t know the status of it but it’s quite interesting: https://github.com/dbuenzli/pvec
It’s a totally different thing, right? A persistent vector is quite different from an imperative vector. The latter should be super lightweight, and afaik there’s no good way of implementing it without Obj
right now. There was a thread on this here last month or so.