|
|
Log in / Subscribe / Register

UB in Rust vs C

UB in Rust vs C

Posted Aug 16, 2024 10:08 UTC (Fri) by intelfx (subscriber, #130118)
In reply to: UB in Rust vs C by mb
Parent article: Standards for use of unsafe Rust in the kernel

> If you construct a reference from a Null-pointer, it is insta-UB. You don't even have to dereference it.
>
> The only way a compiler could "support" this <…>

I will admit I really don’t follow this concept. Why, exactly, is constructing a reference from a null pointer an “insta-UB”?

There is a lot of talk that “Rust does not create UBs from thin air” and that every UB in Rust is “sane” and actually needed to achieve some desirable thing; but what exactly is being achieved by this (as compared to only declaring *dereferencing* an invalid reference an UB)?


to post comments

UB in Rust vs C

Posted Aug 16, 2024 11:38 UTC (Fri) by mb (subscriber, #50428) [Link]

There's no way to encode Null in a &T reference. It would effectively become Option<&T>, which is a different type.
So you can't construct &T from Null, because it can't hold the value.
The thing is impossible at construction time already.

In contrast to that, creating a raw pointer from anything can always hold the value. But you might not be allowed to deref it, because it's not pointing to valid memory, is unaligned or whatever. But the pointer *itself* would hold the intended bit pattern.
(This property is even exploited in some areas https://doc.rust-lang.org/std/ptr/fn.dangling.html)

UB in Rust vs C

Posted Aug 16, 2024 12:22 UTC (Fri) by khim (subscriber, #9252) [Link] (2 responses)

> but what exactly is being achieved by this (as compared to only declaring *dereferencing* an invalid reference an UB)?

Effective fix for the billion dollars mistake, essentially. References, in Rust, couldn't be null, attempting to create such a reference is an instant UB, but Option<&T> can hold None and, more importantly, it's guaranteed that in-memory representation for None in Option<&T> is the exact same thing as null in pointer and it's even guaranteed that it would be the same as null in pointer used by C on that platform!

That means that if you faithfully map nullable pointers to Option<&T> and non-nullable ones to &T then both Rust developers and Rust compiler would know what do you mean (if your function receives &T then you know that checks are not needed, object would be there, 100% guaranteed by the language, and if your function receives Option<&T> then you have to perform that check or else you couldn't dereference it, again language guarantees that).

That's really valuable property and to uphold it an attempt to push null into &T was declared “an instant UB”.

Note that currently even creation of non-null dangling reference is considered UB but that one is under intense debate: it enables some valuable optimizations, but that means that sometime you have to create valid objects from the “thin air”, etc. Before the final decision would be reached it's declared as “currently UB” because adding UB to the language is breaking change and removing it is not and and since it's not entirely clear why would someone need to create a dangling reference (most of the time you may just create a dummy object and pass around reference to that object when needed) it's kept as UB for now.

But that one is debated while attempting to shove null into reference just means you need Option<&T> in that place and it's better for everyone that you would just go and fix the code instead of begging for the dangerous (and pointless) changes to the language.

UB in Rust vs C

Posted Aug 16, 2024 12:49 UTC (Fri) by khim (subscriber, #9252) [Link]

Wrong link. The billion dollar mistake is this one, but I'm pretty sure you saw it already.

UB in Rust vs C

Posted Aug 19, 2024 0:34 UTC (Mon) by intelfx (subscriber, #130118) [Link]

> tive fix for the billion dollars mistake, essentially. References, in Rust, couldn't be null, attempting to create such a reference is an instant UB

Here you're just restating the question and handwaving vigorously. This is not an answer.

>but Option<&T> can hold None and, more importantly, it's guaranteed that in-memory representation for None in Option<&T> is the exact same thing as null in pointer and it's even guaranteed that it would be the same as null in pointer used by C on that platform!

Okay, yeah, so if I correctly understand what you are trying to say here, it's to make niches optimization possible. I didn't think of it.

Null reference as insta-UB

Posted Aug 16, 2024 13:29 UTC (Fri) by farnz (subscriber, #17727) [Link] (5 responses)

There's a theoretical reason, and a practical reason.

First, the theoretical reason: a reference has a validity constraint that it always, unconditionally, refers to a valid place. If you permit a reference to be "null", you now have to change the validity constraint to say that the reference either refers to a valid place, or is null; this is Hoare's "billion dollar mistake". There's a whole pile of things that pile up behind this change; it's not a trivial thing to do, since it affects the semantics of the entire language.

The practical reason is around optimization: if a reference must point to a valid place, then it's always OK to access the place it points to; that, in turn, means that you can write clear code, and have the compiler optimize it to the best code. It can, for example, change a conditional read of a reference to an unconditional read and a conditional use at all the places you had a conditional read, without having to consider the possibility that the conditional use was protecting against the reference being null. And because the compiler is aware of the memory model, it can issue the read much earlier, knowing that there are no memory accesses later in the function that can affect either where the reference points to, or what the value read can be.

Null reference as insta-UB

Posted Aug 21, 2024 20:38 UTC (Wed) by riking (subscriber, #95706) [Link] (4 responses)

Note: "references must point to a valid instance of the object" is actually the safety invariant. The validity invariant is "initialized, non-null, aligned to the alignment of the object".

(What does that mean? It means that unsafe code can temporarily hold references that don't point to valid objects as long as it's careful what it does with them (doesn't try to read) and doesn't let the reference escape into safe code not controlled by the author of the unsafe code.)

Null reference as insta-UB

Posted Aug 21, 2024 21:14 UTC (Wed) by mb (subscriber, #50428) [Link] (2 responses)

But this is only true for "initialized". Even unsafe is not allowed to construct null-references.

Null reference as insta-UB

Posted Aug 21, 2024 22:07 UTC (Wed) by riking (subscriber, #95706) [Link] (1 responses)

The validity invariant is the things that unsafe code can't ever do. The safety invariant is the things it can be careful about and can't let escape to uncontrolled safe code.

Null reference as insta-UB

Posted Aug 21, 2024 22:22 UTC (Wed) by mb (subscriber, #50428) [Link]

Ok, I guess I don't understand your original posting then.

Null reference as insta-UB

Posted Aug 22, 2024 8:11 UTC (Thu) by farnz (subscriber, #17727) [Link]

That's why I said references must point to a valid place, not a valid instance. It's entirely permissible for the place that's pointed at to not be a valid instance, as long as it's a valid place for the referent type to live in.


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds