From 51f7d6815dfbaaacbe5bd1a958eed124ffd88f39 Mon Sep 17 00:00:00 2001 From: Kamil Rojewski Date: Fri, 27 Mar 2020 10:15:17 +0100 Subject: [PATCH 1/4] no async attribute --- text/0000-no-async.md | 81 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 text/0000-no-async.md diff --git a/text/0000-no-async.md b/text/0000-no-async.md new file mode 100644 index 00000000000..799271812cf --- /dev/null +++ b/text/0000-no-async.md @@ -0,0 +1,81 @@ +- Feature Name: `no_async` +- Start Date: 2020-03-27 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +This feature enables tagging functions as dangerous/error-prone to run within an async context. + +# Motivation +[motivation]: #motivation + +Some functions are not safe to call within an async context. A trivial example might be locking +a standard mutex in an async block - without proper care we risk deadlocking the process. Rust +currently doesn't check such situations at compile time, which might lead to hard to debug runtime +errors. While it's true such dangerous calls are logic errors, not related to language itself, +it would be very helpful if programmers could mark functions as potentially hazardous to call in +async code, which would result in a compile warning. This mechanism is similar to detecting async +code inside locks in C#. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +This feature adds a new `#[no_async]` attribute which is permitted on functions. Whenever a call +to such annotated function is called inside a `impl Future` code, a warning is raised to indicate +a potentially dangerous operation: + + #[no_async] + fn foo1() { + } + + #[no_async("custom message")] + fn foo2() { + } + + async fn bar() { + // generates a generic compile-time warning + foo1(); + + // generates a "custom message" compile-time warning + foo2(); + } + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +In each `impl Future` code, including implicitly generated, a check needs to be made at function calls +for the presence of the new attribute, and a warning needs to be raised if such is present. + +# Drawbacks +[drawbacks]: #drawbacks + +Checking for such attribute at call sites might increase compile time. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +This attribute enables programmers to explicitly communicate which API is not intended to be used in async +code. Alternatively, we can make this process automatic (which is not trivial) or keep the situation as it +is now - no checking and potential runtime errors. Having an attribute seems like an easy solution, which +can be used by external library authors. + +# Prior art +[prior-art]: #prior-art + +C# compiler detects awaiting on async methods inside lock-guarded code and emits an error in such case. Given +a lock is not a special instruction in Rust, it's better to provide a generic mechanism which solves the +problem the other way around. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- Maybe a hard error is more appropriate than a warning? + +# Future possibilities +[future-possibilities]: #future-possibilities + +This RFC paves a way for extended async code validation. Having syntactically valid code in async blocks is +not enough - we also need to provide a mechanism for people to add logical validation to avoid runtime +problems. From 675392577eb49de59d765d1672866ee17b83e71a Mon Sep 17 00:00:00 2001 From: Kamil Rojewski Date: Fri, 27 Mar 2020 18:23:51 +0100 Subject: [PATCH 2/4] switched from an attribute to a trait --- text/0000-no-async.md | 81 ------------------------------- text/0000-yield-safe.md | 104 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 81 deletions(-) delete mode 100644 text/0000-no-async.md create mode 100644 text/0000-yield-safe.md diff --git a/text/0000-no-async.md b/text/0000-no-async.md deleted file mode 100644 index 799271812cf..00000000000 --- a/text/0000-no-async.md +++ /dev/null @@ -1,81 +0,0 @@ -- Feature Name: `no_async` -- Start Date: 2020-03-27 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) - -# Summary -[summary]: #summary - -This feature enables tagging functions as dangerous/error-prone to run within an async context. - -# Motivation -[motivation]: #motivation - -Some functions are not safe to call within an async context. A trivial example might be locking -a standard mutex in an async block - without proper care we risk deadlocking the process. Rust -currently doesn't check such situations at compile time, which might lead to hard to debug runtime -errors. While it's true such dangerous calls are logic errors, not related to language itself, -it would be very helpful if programmers could mark functions as potentially hazardous to call in -async code, which would result in a compile warning. This mechanism is similar to detecting async -code inside locks in C#. - -# Guide-level explanation -[guide-level-explanation]: #guide-level-explanation - -This feature adds a new `#[no_async]` attribute which is permitted on functions. Whenever a call -to such annotated function is called inside a `impl Future` code, a warning is raised to indicate -a potentially dangerous operation: - - #[no_async] - fn foo1() { - } - - #[no_async("custom message")] - fn foo2() { - } - - async fn bar() { - // generates a generic compile-time warning - foo1(); - - // generates a "custom message" compile-time warning - foo2(); - } - -# Reference-level explanation -[reference-level-explanation]: #reference-level-explanation - -In each `impl Future` code, including implicitly generated, a check needs to be made at function calls -for the presence of the new attribute, and a warning needs to be raised if such is present. - -# Drawbacks -[drawbacks]: #drawbacks - -Checking for such attribute at call sites might increase compile time. - -# Rationale and alternatives -[rationale-and-alternatives]: #rationale-and-alternatives - -This attribute enables programmers to explicitly communicate which API is not intended to be used in async -code. Alternatively, we can make this process automatic (which is not trivial) or keep the situation as it -is now - no checking and potential runtime errors. Having an attribute seems like an easy solution, which -can be used by external library authors. - -# Prior art -[prior-art]: #prior-art - -C# compiler detects awaiting on async methods inside lock-guarded code and emits an error in such case. Given -a lock is not a special instruction in Rust, it's better to provide a generic mechanism which solves the -problem the other way around. - -# Unresolved questions -[unresolved-questions]: #unresolved-questions - -- Maybe a hard error is more appropriate than a warning? - -# Future possibilities -[future-possibilities]: #future-possibilities - -This RFC paves a way for extended async code validation. Having syntactically valid code in async blocks is -not enough - we also need to provide a mechanism for people to add logical validation to avoid runtime -problems. diff --git a/text/0000-yield-safe.md b/text/0000-yield-safe.md new file mode 100644 index 00000000000..adc8c95ad97 --- /dev/null +++ b/text/0000-yield-safe.md @@ -0,0 +1,104 @@ +- Feature Name: `yield_safe` +- Start Date: 2020-03-27 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +This feature enables giving types a default auto trait `YieldSafe` which permits them to be used in an async context. +Consequently, it's possible to mark a type as `!YieldSafe` which disallows yielding control in an async block, while an +object of such type is alive at that point. + +# Motivation +[motivation]: #motivation + +Some operations are not safe to be performed within an async context. A trivial example might be locking a standard +mutex in an async block - without proper care we risk deadlocking the process. Rust currently doesn't check such +situations at compile time, which might lead to hard to debug runtime errors. While it's true such dangerous operations +are logic errors, not related to language itself, it would be very helpful if there was a mechanism to mark types as +potentially hazardous to use in async code, which would result in a compile error. This mechanism is similar to +detecting async code inside locks in C#. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +This feature adds a new `YieldSafe` default auto trait which propagates according to the same rules as other auto +traits. Yield-safe types can be alive at a point of yielding control, e.g. `.await`-ing a Future. At the same time, no +`!YieldSafe` object can cross a control yield boundary. + + struct Widget; + + async fn foo() { + } + + async fn bar() { + // valid code, since Widget is YieldSafe by default + let w = Widget; + foo().await + } + + struct Gadget; + + impl !YieldSafe for Gadget {} + + async fn invalid() { + // compile-time error, since Gadget is not YieldSafe + let g = Gadget; + foo().await + } + + // need to explicitly mark type as YieldSafe + async fn potentially_invalid(value: Box) { + foo().await + } + + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +New `YieldSafe` default auto trait needs to be implemented for all primitive types and follow the same propagation rules +as other auto traits. Each control yield point would need to check if only `YeldSafe` objects are alive at that point. +Generic, `impl` and `dyn` types need also take yield safety into account, to avoid circumventing the system. + +One example usage would be a standard `MutexGuard` which is error-prone to use across `.await` points. We would need to +identify other standard types which are similarly dangerous. + +# Drawbacks +[drawbacks]: #drawbacks + +A new auto trait might involve backwards incompatible changes, especially where generic types currently cross yield +points. The compiler could limit the problem by checking if the effective lifetime of objects of such types actually +cross yield boundary, e.g. + + async fn foo(value: T) { + // potentially valid code, since value doesn't need to cross .await if it doesn't implement Drop (recursively) + bar().await + } + +We also could provide a grace period by emitting a warning instead of an error. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- Early solution involved a custom attribute which marked functions as potentially dangerous to call in an async + context, but that solution proved to be inadequate. +- The effect of this solution is close to the one seen in C#. + +# Prior art +[prior-art]: #prior-art + +C# compiler detects awaiting on async methods inside lock-guarded code and emits an error in such case. Given a lock is +not a special instruction in Rust, it's better to provide a generic mechanism which solves the problem. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- How to handle more granular control, e.g. yield-unsafe functions? +- How to handle backwards compatibility better? + +# Future possibilities +[future-possibilities]: #future-possibilities + +This RFC paves a way for extended async code validation. Having syntactically valid code in async blocks is not enough - +we also need to provide a mechanism for logical validation to avoid runtime problems. From c4d9b98a5bc8b537aed6e60c873bbd5300bad893 Mon Sep 17 00:00:00 2001 From: Kamil Rojewski Date: Fri, 3 Apr 2020 08:28:17 +0200 Subject: [PATCH 3/4] AssertYieldSafe wrapper --- text/0000-yield-safe.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/text/0000-yield-safe.md b/text/0000-yield-safe.md index adc8c95ad97..95f2c1e39f3 100644 --- a/text/0000-yield-safe.md +++ b/text/0000-yield-safe.md @@ -4,6 +4,7 @@ - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary + [summary]: #summary This feature enables giving types a default auto trait `YieldSafe` which permits them to be used in an async context. @@ -11,6 +12,7 @@ Consequently, it's possible to mark a type as `!YieldSafe` which disallows yield object of such type is alive at that point. # Motivation + [motivation]: #motivation Some operations are not safe to be performed within an async context. A trivial example might be locking a standard @@ -21,6 +23,7 @@ potentially hazardous to use in async code, which would result in a compile erro detecting async code inside locks in C#. # Guide-level explanation + [guide-level-explanation]: #guide-level-explanation This feature adds a new `YieldSafe` default auto trait which propagates according to the same rules as other auto @@ -52,9 +55,29 @@ traits. Yield-safe types can be alive at a point of yielding control, e.g. `.awa async fn potentially_invalid(value: Box) { foo().await } - + +Additionally, an `AssertYieldSafe` wrapper is introduced to mark enclosed variables as `YieldSafe`, thus avoiding +compile errors when they cross yield boundaries: + + use std::sync::Mutex; + + async fn foo() { + } + + async fn bar() { + let m = Mutex::new(0); + + // the following would result in a compile error, since MutexGuard is not YieldSafe + // let lock = m.lock(); + // foo().await + + // the following compiles without errors, but the user is responsible for ensuring logical correctness + let lock = AssertYieldSafe(m.lock()); + foo().await + } # Reference-level explanation + [reference-level-explanation]: #reference-level-explanation New `YieldSafe` default auto trait needs to be implemented for all primitive types and follow the same propagation rules @@ -64,7 +87,11 @@ Generic, `impl` and `dyn` types need also take yield safety into account, to avo One example usage would be a standard `MutexGuard` which is error-prone to use across `.await` points. We would need to identify other standard types which are similarly dangerous. +`AssertYieldSafe` is a counterpart of `AssertUnwindSafe` with the differences of implementing `YieldSafe` instead of +`UnwindSafe`/`RefUnwindSafe` and not implementing `Future`. + # Drawbacks + [drawbacks]: #drawbacks A new auto trait might involve backwards incompatible changes, especially where generic types currently cross yield @@ -79,6 +106,7 @@ cross yield boundary, e.g. We also could provide a grace period by emitting a warning instead of an error. # Rationale and alternatives + [rationale-and-alternatives]: #rationale-and-alternatives - Early solution involved a custom attribute which marked functions as potentially dangerous to call in an async @@ -86,18 +114,21 @@ We also could provide a grace period by emitting a warning instead of an error. - The effect of this solution is close to the one seen in C#. # Prior art + [prior-art]: #prior-art C# compiler detects awaiting on async methods inside lock-guarded code and emits an error in such case. Given a lock is not a special instruction in Rust, it's better to provide a generic mechanism which solves the problem. # Unresolved questions + [unresolved-questions]: #unresolved-questions - How to handle more granular control, e.g. yield-unsafe functions? - How to handle backwards compatibility better? # Future possibilities + [future-possibilities]: #future-possibilities This RFC paves a way for extended async code validation. Having syntactically valid code in async blocks is not enough - From 334c70299fc6ee5c8c5e629677f64ab58d35772e Mon Sep 17 00:00:00 2001 From: Kamil Rojewski Date: Tue, 20 Oct 2020 08:36:31 +0200 Subject: [PATCH 4/4] typo fix Co-authored-by: Chan Kwan Yin --- text/0000-yield-safe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-yield-safe.md b/text/0000-yield-safe.md index 95f2c1e39f3..9f14fab9d55 100644 --- a/text/0000-yield-safe.md +++ b/text/0000-yield-safe.md @@ -81,7 +81,7 @@ compile errors when they cross yield boundaries: [reference-level-explanation]: #reference-level-explanation New `YieldSafe` default auto trait needs to be implemented for all primitive types and follow the same propagation rules -as other auto traits. Each control yield point would need to check if only `YeldSafe` objects are alive at that point. +as other auto traits. Each control yield point would need to check if only `YieldSafe` objects are alive at that point. Generic, `impl` and `dyn` types need also take yield safety into account, to avoid circumventing the system. One example usage would be a standard `MutexGuard` which is error-prone to use across `.await` points. We would need to