3 ms·
The Rust FFI takes all pointers as "raw pointers". The reason for this is that Rust's various safe pointer types have a lot of extra conditions on them to enfor
by johncolanduoni 11y ago
The Rust FFI takes all pointers as "raw pointers". The reason for this is that Rust's various safe pointer types have a lot of extra conditions on them to enforce memory/data race safety without garbage collection. For example, some pointers are uniquely owned and will be freed when they go out of scope, some can't be stored because they are "borrowed", some can't be mutated because they're shared etc. When Rust gets a pointer from another language like C (that obviously doesn't have such semantics), whoever is writing the FFI has to decide what category these raw pointers fall under before passing them back to "safe" code.
Depending on the type of semantics you are attaching to the pointer, you can use something like this[1] function, which does exactly what you're asking. It is a runtime no-op, but it coerces the unsafe pointer type so that memory safe code can use it (based on your word that it can be interpreted in this way).
[1]: https://doc.rust-lang.org/std/primitive.pointer.html#method.as_ref https://doc.rust-lang.org/std/primitive.pointer.html#method....
- derefr 11y agoTo be clear, I wasn't so much imagining using the FFI declaration to vouchsafe the opaque handle into a completely Rust-nativized memory-safe owned reference; but rather, as taking the opaque nullable pointer from the FFI, and unwrapping it exactly one level into an Option<opaque non-nullable pointer>—that is, turning a nullable FFI "handle" into an Option<non-nullable FFI "handle">. Such an FFI function declaration really can't require that the thing returned inside the Option be a dereference of the pointer: in the happy case, it's still an opaque FFI handle, so dereferencing it just exposes an opaque struct you shouldn't touch; and in the unhappy case, it's still an invalid pointer, just one that's invalid because somebody else (like the library that gave it to you) freed it. The raw pointer itself, even if non-NULL, is still "tainted"; you still want to apply manual runtime checks to it before casting it to a Rust reference. (And, past that, in both cases you still probably want to deal with the pointer mostly just by passing it back to other FFI functions from the same library, treating the pointer itself as an identifier, a key the API associates with something.) I'm emphasizing this distinction because the NULLness of the pointer is, from what I can see, "outside" the lifetime of the reference, and even outside the "identifier-ness" of the handle. Knowing the validity of the pointer requires knowing about ownership/lifetime; the pointer might point to memory that has been free()ed. But you can know that the pointer is intentionally NULL (and therefore not a valid handle/identifier from the FFI's perspective) without attempting to construct a reference. Effectively, a [nullable] raw pointer is the moral equivalent of a tagged data structure. There's a piece of metadata you can get from the tag: when it's zero, the contained raw pointer is intentionally invalid; when it's nonzero, the contained raw pointer is only maybe-invalid. You can destructure and act on the "tag" without doing anything else to the [non-nullable] raw pointer "stored inside". I wouldn't expect most languages to care about a distinction this fine, because if a language has references, it really only wants to think about "pointers: maybe invalid" and "references: always valid". But Rust is in the unique position of trying to work "directly with C" without the impedance mismatch of glue like SWIG. And C provides many libraries that return nominally opaque handles, but expect their consumers to notice if those handles are NULL (which happens often, when e.g. a factory-function from such a library is called with NULL arguments, or unexpectedly receives a NULL from a library it itself consumes.)
- johncolanduoni 11y agoWell, the answer to that is somewhat complicated. In Rust, a raw pointer has the same semantics of a C pointer. Could be null, could point to nowhere, the object pointed to could live temporarily, could live forever etc. However, there aren't any pointers which only weaken some of these conditions. The function I linked takes a possibly null raw pointer, and vouches that: 1. It points to null or something. 2. The object it points to (if it is not null) will live at least as long as the current scope. 3. The object it points to will not be mutated as long as the current scope is active. Practically speaking, as long as you don't do anything with the resulting Option<&T> other than check if it is empty, you're safe. In addition, Rust will preserve the internal pointer address, so you could bring it back to the FFI. So you could treat it consistently as such, you'll be fine. I'm not sure if the language spec guarantees that, because that's not how Rust references are designed to be used, but it won't matter in practice. For what you want, you could just create a generic structure that holds a pointer to the given type, and lets you ask if the contained pointer is null. That way you won't add any false guarantees to the pointer (e.g. immutability, lifetime, etc.).