4 ms·
Yes, that's technically true, but you have to admit a little contrived, especially as pertaining to function pointers/references. I'll amend my statement to "u
by smitherfield 9y ago
Yes, that's technically true, but you have to admit a little contrived, especially as pertaining to function pointers/references.
I'll amend my statement to "unlike pointers, valid references cannot point to a null address."
This is also true of any other language which has both nullable and non-nullable references, and a way to convert the former to the latter without accounting for the null case, such as Rust[1].
fn pass_null_reference() {
unsafe { print_i32(&*std::ptr::null()) }
}
fn print_i32(n: &i32) {
println!("{}", n) // segfault
}
[1] https://godbolt.org/g/1oUnNt https://godbolt.org/g/1oUnNt
- nybble41 9y agoAh, but what makes it an invalid reference, apart from convention? You could just as easily say that a null pointer is an invalid pointer in a context where a non-null value is expected. After all, a reference is just a constant pointer which is automatically dereferenced. Any program which may contain a null pointer may also contain a null reference. "This is also true of any other language which has both nullable and non-nullable references, and a way to convert the former to the latter without accounting for the null case, such as Rust." Indeed, though in Rust (like most other safe-by-default languages) you do need that glaringly obvious "unsafe" keyword to perform the conversion, which is a hint that you should be checking for null at that point.
- smitherfield 9y ago> Ah, but what makes it an invalid reference, apart from convention? It's an invalid reference in that a reference which does not refer to an object is either dead code, or an unrecoverable error. There's no point in checking if a reference refers to null, because (unlike pointers) it cannot then be assigned a useful value, and also because any such check will be removed by the optimizer, because null references are undefined behavior (in both C++[1] and Rust[2]). > Indeed, though in Rust (like most other safe-by-default languages) you do need that glaringly obvious "unsafe" keyword to perform the conversion, which is a hint that you should be checking for null at that point. It is an improvement but (obviously) the unsafety in this case escapes from the "unsafe" block into the surrounding "safe" code and other "safe" functions. It's also possible, even in entirely-"safe" code, to crash a Rust program by attempting to dereference a null pointer. fn dereference(nullable_ref: Option<&i32>) -> &i32 { nullable_ref.unwrap() } fn crash() { dereference(None); } [1] https://godbolt.org/g/TjexTp https://godbolt.org/g/TjexTp [2] https://godbolt.org/g/eeXAnn https://godbolt.org/g/eeXAnn
- steveklabnik 9y agoPanics are absolutey safe compared to segfault. “Memory safety” does not mean “this program never terminates unexpectedly.” You can also drop all the 'a business in that definition, it's superfluous.
- nybble41 9y ago"There's no point in checking if a reference refers to null, because (unlike pointers) it cannot then be assigned a useful value, and also because any such check will be removed by the optimizer, because null references are undefined behavior (in both C++ and Rust)." That is a good point; I did not realize it was considered undefined behavior, which does make it substantially different from a null pointer. (Of course, that means the compiler remains free to check for null when the reference is created, which would have been the safer approach. The check would only be needed when creating a reference from a pointer, and even then it could be optimized out if the pointer is known to be non-null.) "It is an improvement but (obviously) the unsafety in this case escapes from the "unsafe" block into the surrounding "safe" code and other "safe" functions." Right. Despite the name, the "unsafe" keyword is not really meant to say that the code inside the block is unsafe, but rather that the compiler is not responsible for its safety—it is up to the programmer to expose a safe interface. This code breaks that rule, and the result is undefined behavior. The constraint does help in narrowing down the source of the problem to a small core of functions explicitly marked as unsafe, rather than the entire codebase. As steveklabnik pointed out, the safe version (unwrapping a None value) results in a panic call rather than actually dereferencing a null pointer, meaning that you can reliably intercept it with std::panic::set_hook—it is not undefined behavior.