We've deprecated mem::uninitialized, but I'm not sure what the recommended way to synthesize zero-sized types is.
If a type is known to be zero-sized (e.g. a Fn type with no capture) is it safe to initialize an instance of it with mem::zeroed or mem::uninitialized?
I mean, semantically, I would hope not, especially if you're using a ZST as some kind of scoped witness type. I think it's about as bad as forging a raw pointer.
You probably need to qualify what kind of thing you want to pull out of the aether better than "any zero-sized type".
Yeah, I should have asked if it's UB to operate on them, not if it's unsafe, it should definitely be unsafe to deal with zero-sized tokens and such.
I don't have to deal with never-sized types here, since the crate I'm writing does have access to values of this type before it kicks off the series of events that lead to the value eventually being conjured. The only reason I can't pass that specific value down is that closures aren't serializable and this is cross-process.
The whole approach in the mitosis crate is broken, because there is no guarantee that the closure code exists and is at the same address in the new process (e.g. it could be from a dynamically loaded library that could not be loaded or loaded at an arbitrary address, and the "ASLR mitigation" doesn't work unless you know which library the code is from).
What you should do is simply pass the closure to call in the child to the init function and not to the spawn function, and only let the user specify a serializable message at the spawn point.
If you really insist on putting the code at the spawn point, then define a macro and have it generate a C++ global constructor that registers a bare fn associated with a GUID with a central register, and send the GUID to the child process. It will still not work in the case of libraries that are not loaded, but it will fail gracefully rather than starting to execute code at an arbitrary address.
Uh, that's why the actual pointer being shipped over is a function pointer that I control, not an arbitrary function pointer being created by the user. The user can only pass down zero sized closures and fn item types, which get wrapped in a monomorphized fn that I convert to a pointer. The ASLR mitigation is operating on pointers from the same library.
I wonder what it would take to make it possible to ship across a capturing, serializeable (FnOnce() -> R), i.e., having a way to decompose a closure into its captures and its body, and to implement traits (like serialization) on the closure captures... I seem to recall a discussion about this on the order of 6-12 months ago??
Sorry, I got confused a bit, but I think it may still potentially broken, because run_func() code is going to be generated during monomorphization in the crate calling spawn, whereas init() is generated in the mitosis crate, and I think that could end up in a different dynamic library (unless Cargo is prevented from ever doing so, which may be the case already?).
A macro still seems better since it allows to statically assert that the closure has no data by trying to convert it to a bare fn (passing a bare fn doesn't work, because then run_func is no longer monomorphized, and the bare fn could be in any library, which is what I was mistakenly thinking about).
Pretty sure it does do Rust dylibs, if Cargo.toml contains crate-type = ["dylib"]. mitosis presumably doesn't have that, but I think it could still break if someone makes an intermediate crate that is a dylib, depends on mitosis, and re-exports its symbols.
I don't think that's even true, in general. What if the semantic is that the ZST is a token that a one-time initialization has happened, and you can hand it out like candy to anything that needs to know about the initialization?
But this is about safety: regarding validity, I think that point is already addressed.
Just to leave it said I have a zero sized type which is Copy but is unsafe to construct,because it has a safety invariant that can't be upheld in the type system,which is that the FieldPathSet<_,UniquePaths> represents a set of disjoint fields.
That said,if there is already a soundly constructed instance of the specific FieldPathSet<T,UniquePaths> type,it would be sound to construct with std::mem::zeroed().