I assume you’re asking whether this API would be sound. Note that even if it was sound, it would not really be necessary since we can build an even easier to use API in the form of the pin! macro. Yes, it’s a macro, but macros aren’t all that bad, and also, there is no known way to avoid the need for a macro, as far as I’m aware, so a macro is the only option anyway. Which makes post like yours somewhat interesting since the premise is likely thar you're trying to change that and demonstrate a way to avoid macros after all.
Note that you do need to study the safety requirements for Pin carefully though. And indeed this API is unsound under those requirements, since it allows a user to circumvent the drop guarantee of pinning by wrapping the PinCell in a ManuallyDrop. Rust Playground
For further discussion/information/context on the drop guarantee and why it might be useful, feel free to take a look at this semi-recent thread where a different user presented their own idea of a presumably-sound macro-free stack-pinning API that failed to be sound due to the drop guarantee.
For questions about API design unrelated to the standard library, deep free to move over to users.rust-lang.org, which would be the better place to ask/discuss those.
Also try to make your questions a bit longer / explain more clearly what you are asking, including necessary context; e. g. in case of API design, what the API is there for, etc. That makes it easier to answer.
The pinning guarantee is not sufficient for thread::scope. The reason is that while a pinned value is guaranteed to be dropped before it is invalidated (the memory is freed/reused), there's no guarantee that it actually gets dropped.
This is why there's no async equivalent to thread::scope. If you yield/await inbetween creating the scope! and dropping it, and that yield never resumes, then the threads are never joined even though the borrow lifetime they have access to is not prevented from expiring.
A closureless scope! macro is probably sound if only used in synchronous functions, but there's no way to enforce that.
async fn ouch(r: &mut [u8]) {
let s = scope!();
s.spawn(|| loop {
*r.choose_mut(&mut thread_rng()).unwrap() = random();
});
loop {
// yield from Future::poll
future::pending::<()>().await;
}
unreachable!();
}
fn bang() {
let mut data = Box::new([0; 1024]);
let mut fut = Box::pin(ouch(&mut *data));
fut.as_mut().now_or_never(); // poll once
std::mem::forget(fut); // leak future
drop(data); // drop data it borrowed
}
This compiles (playground). The spawned thread continues to access the data given to the async fn after its lifetime has expired, because the thread was never joined, because the future holding the thread handle was just leaked, not dropped.
If you only borrow data owned by the future, no issues result. But async fn can borrow data that they don't own; allowing such is one of the main points of async Rust.
Leaking things is sound and allowed in safe code. If something is pinned to a synchronous function's stack, you're guaranteed that it will be dropped before any lifetimes is captures expire. If something is pinned anywhere else, you're guaranteed that it will be dropped before it's deallocated, but if it's leaked and never deallocated, it can outlive the lifetimes that it captures, because it's unusable after that lifetime. (Unless you bypass that with unsafe, such as done with scope! allowing spawning threads with access to non-'static borrows terminated not by the lifetime but by the (safely but now unsoundly skippable) drop.)
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
--> src\main.rs:56:13
|
56 | () => {{a()}}
| ^^^ call to unsafe function
...
60 | test!()
| ------- in this macro invocation
|
= note: consult the function's documentation for information on how to avoid undefined behavior
= note: this error originates in the macro `test` (in Nightly builds, run with -Z macro-backtrace for more info)