Hello. I'm working on a graph processing crate for Rust. The documentation is quite cryptic at this point, so I hope examples are more or less self-explanatory.
It seems promising so far, but I've recently come up with a way to radically streamline its API, which is unfortunately illegal according to Rustonomicon. Every graph is a collection of structures such as this one: https://docs.rs/dynamic_graph/0.0.3/src/dynamic_graph/lib.rs.html#75-78 The payload is an arbitrary data stored in a graph node, refs is a collection of pointers to other nodes in the same graph.
Since I can't allow users to use raw pointers, a special type called GraphPtr is used in the public API. As you can see, this is simply a #[repr(transparent)] wrapper of an ordinary NonNull pointer, except it uses this crate to ensure that every GraphPtr is always legal to dereference. Ergo, it is exactly the same as a normal pointer at runtime.
Intuitively this means any collection which holds NonNull should also look the same as a one holding GraphPtr
If this were the case, this no-op method and its friends would've been totally safe.
This actually works on my machine (Win10x64 + Rust 1.40), but it might inexplicably break on any update should the language team decide to do something weird with layout optimization.
If this hack will become a safe operation I will be able to replace a ton of boilerplate methods with Deref and Index traits, allow to trivially use user-defined graph structures and simply do away with the whole concept of Cursors.
I would like to get the following guarantee:
Any generic struct, tuple or enum Type<T, C>, must be layout compatible with Type<Tr, C> where T and Tr are sized copy types and Tr is #[repr(transparent)] of T.
- It is a user's responsibility to ensure Tr behaves identically to T inside Type.
- Non-generic types with the same fields and compiler-generated types like Futures and Closures don't need to adhere to this guarantee.
I'd like to know:
-
Is there actual work required to implement my request, besides not breaking stuff that already works?
-
Is there a realistic scenario when my suggestion blocks an important optimization?
Alternative solutions:
- Fork std::Collections into a separate crate and redefine every struct with #[repr(c)]
- A wrapper for every collection type which casts normal NonNull into my GraphPtr before giving it to a user.
These solutions are a hassle to maintain, don't provide 1-to-1 mapping of interfaces and may turn out non-zero cost in some scenarios.
-
Add a new #[repr()] which is the same as 'transparent' plus my thing.
-
Add a magical type for wrappers of this kind: SameLayout<T, Marker : !Sized> which does the same thing, but differently.
I have no idea if 3 and 4 make any difference implementation-wise, but these are the options I'm perfectly fine with if they do.