[ANN] vec 0.1.0

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!

4 Likes

I’m curious how you would compare this with Containers.Vector?

3 Likes

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 CCVectors 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)

1 Like

Super nitpicky here, but rust doesn’t have write-only either :slightly_smiling_face:

1 Like

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 :sweat_smile:), 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).

1 Like

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

4 Likes

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.

1 Like