-
-
Notifications
You must be signed in to change notification settings - Fork 484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed large swaths of unsoundness in nalgebra
.
#949
Conversation
In the current implementation, we've lost the capability to use |
Thank you a lot @OfficialURL for all your efforts on this. You have done a fantastic job exploring the various approaches. Since you told me you may no longer have much time for this, I am taking over as agreed. The single
|
I would like to try out this branch on my code base shortly. In the meantime, I have a question about |
The public API of the |
Ah, thanks for clarifying! I tried to quickly zoom through the commit to figure it out, but it was hard to get an overview from a brief glance. |
The CI passes now. The main remaining problem are the uses of |
All right, so with some minor fixes only related to the changes to I looked through the docs a little. Shouldn't methods like |
By the way, one slight "regression" in the EDIT: This isn't necessarily a big deal (for me certainly not), but I thought it worth mentioning. |
Yes, I’ve removed them this morning. I’m finishing cleaning everything up today.
It’s not really a regression because passing an uninitialized Matrix was unsound in the first place (even though it didn’t cause any trouble in practice). If someone really wants to use an uninitialized matrix, they will be able to use the |
That's excellent, thanks for clearing that up :) |
Great work once again with spearheading the initiative to remove this source of unsoundness in the design of It's been an open issue for such a long time, it's amazing to see this fixed! 🎉 |
Uninitialized data buffers are used throughout the
nalgebra
codebase. As per the Rust docs, the correct way to use an uninitialized buffer would be to declare aStorage<MaybeUninit<T>, _, _>
, initialize all of its entries, and then somehow convert it into aStorage<T, _, _>
. However,nalgebra
skips theMaybeUninit
step altogether. This isn't much of a problem whenT
is a primitive numeric type (under the current compiler!), but ifT
has invalid bit combinations or needs to be dropped, this can cause serious trouble. Consequently, most code innalgebra
can currently trigger UB. 😰This PR aims to solve this issue. It will be marked as a Draft until we rigorously check safety requirements throughout the new code, make the transition from the previous code simpler, polish some negative consequences of this change, and until the other
nalgebra
crates are rid of these same issues.Changes
Solving this issue required an extensive rework of some core traits of the library. I explain some of the more important ones:
Scalar
trait redefinitionPreviously,
Scalar
served as the monolithic trait that bounded the data type in every single data structure innalgebra
. It had the following traits as sub-traits:This was immediately problematic, since
MaybeUninit<T>
does not implementPartialEq
, and only implementsClone
whenT: Copy
.To get around this,
Scalar
was demoted from being a universally required trait to merely being a convenient marker for methods with clear numerical purpose. That is,T: Scalar
is now only required for numerical methods on matrices, and is no longer required for methods that merely treat matrices as a data storage.The
PartialEq
bound was removed, as the vast majority of methods make no use of it (and those that did could simply use a trait bound). This restuls in the currentScalar
trait:The
'static
bound is currently only used to optimize matrix multiplication on floating point numbers (specificallygemm
), viaTypeId::of
. If we wanted to, we could replace this use by associated methodsis_f32
andis_f64
. The remaining trait bounds are essential, since there's very little we can numerically operate on a matrix without cloning (unless we use some highly unsafe moves), and since debugging generic code would be almost impossible without theScalar: Debug
bound.Allocator
trait redefinitionThe
Allocator
trait allows for matrix storage allocation to be specialized, depending on whether any of its dimensions areDynamic
. Formerly, it consisted only of aBuffer
associated type, and anallocate_from_iterator
associated method. This simpler trait has been renamedInnerAllocator
.The new
Allocator
trait looks like this:Using this more specific trait is almost never an issue, and is in fact encouraged, as it makes code future-proof while not actually changing the types you can use your method with.
Various methods for uninitialized buffers
The following methods have been added or reworked for convenience of working with uninitialized data storage.
new_uninitialized_generic
: allocates a matrix with an owned uninitialized storage.assume_init
: retrieves a matrix with an initialized storage out of an uninitialized one.assume_init_ref
/assume_init_mut
: same as above, but don't take ownership of the matrix and return a full slice of it instead.Other minor changes
There's a few other changes I made and methods I implemented, mostly out of convenience.
unsafe
typecasts to get rid of a few clones here and there.#[repr(C)]
into#[repr(transparent)]
, got rid of others altogether.