Skip to content

Commit a3d24bb

Browse files
committed
Add #[diagnostic::blocking] attribute
`#[diagnostic::blocking]` is a marker attribute for functions that are considered (by their author) to be a blocking operation, and as such shouldn't be invoked from an `async` function. `rustc`, `clippy` and other tools can use this signal to lint against calling these functions in `async` contexts. It would allow `rustc`, `clippy` and other tools to provide lints like ``` warning: async function `foo` can block --> $DIR/blocking-calls-in-async.rs:28:1 | 28 | async fn foo() { | -------------- 29 | interesting(); | ^^^^^^^^^^^^^`foo` is determined to block because it calls into `interesting` | note: `interesting` is considered to be blocking because it was explicitly marked as such --> $DIR/blocking-calls-in-async.rs:5:1 | 5 | #[diagnostic::blocking] | ^^^^^^^^^^^^^^^^^^^^^^^ 6 | fn interesting() {} | ---------------- ```
1 parent 72ba5cc commit a3d24bb

File tree

1 file changed

+82
-0
lines changed

1 file changed

+82
-0
lines changed

text/0000-diagnostic-blocking.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
- Feature Name: `diagnostic_blocking`
2+
- Start Date: 2024-05-17
3+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
`#[diagnostic::blocking]` is a marker attribute for functions that are considered (by their author) to be a blocking operation, and as such shouldn't be invoked from an `async` function. `rustc`, `clippy` and other tools can use this signal to lint against calling these functions in `async` contexts.
10+
11+
# Motivation
12+
[motivation]: #motivation
13+
14+
Calling blocking operations in `async` functions is a common, well publicized foot-gun, but we don't provide nearly enough automated checks against it currently. Adding this annotation will allow `rustc` and `clippy` to detect the "trivial" cases (direct call of blocking operation within an `async fn`), while leaving the door open for future exhaustive analysis by these or other tools (perform DFS exploration of the crate's call graph looking for *transitive* calls to blocking operations invoked from `async` functions). Making this an annotation that users can add to their own APIs greatly adds to the utility of this linting, expanding coverage significantly.
15+
16+
# Guide-level explanation
17+
[guide-level-explanation]: #guide-level-explanation
18+
19+
`#[diagnostic::blocking]` is an attribute that can annotate `fn` items. It signals to the compiler and other tools that the annotated function can perform blocking operations, making it unsuitable to be called from `async` contexts.
20+
21+
The Rust compiler/clippy will do a best effort analysis to lint against uses of blocking operations from `async` contexts, relying on this annotation, but it is not assured it will.
22+
23+
```
24+
warning: async function `foo` can block
25+
--> $DIR/blocking-calls-in-async.rs:28:1
26+
|
27+
28 | async fn foo() {
28+
| --------------
29+
29 | interesting();
30+
| ^^^^^^^^^^^^^`foo` is determined to block because it calls into `interesting`
31+
|
32+
note: `interesting` is considered to be blocking because it was explicitly marked as such
33+
--> $DIR/blocking-calls-in-async.rs:5:1
34+
|
35+
5 | #[diagnostic::blocking]
36+
| ^^^^^^^^^^^^^^^^^^^^^^^
37+
6 | fn interesting() {}
38+
| ----------------
39+
```
40+
41+
# Reference-level explanation
42+
[reference-level-explanation]: #reference-level-explanation
43+
44+
The barest version of this feature is the addition of the `blocking` attribute to the `diagnostic` namespace, *and nothing else*, with no behavioral change. This would allow clippy and other tools to use the mark for their own analysis.
45+
46+
The next step up is for `rustc` to incorporate a lint for direct calls. This would be trivial to implement.
47+
48+
The final version would be to implement a call-graph analyisis lint so that transitive calls to blockign operations also produce a warning, but this is explicitly out of scope of this RFC.
49+
50+
# Drawbacks
51+
[drawbacks]: #drawbacks
52+
53+
There is a risk crate authors will *over*annotate their APIs with `#[diagnostic::blocking]` to get around temporary lacks in the analysis. Crate authors might *also* overannotate their APIs as a way of discouraging calls from `async` scopes, even if they are not blocking.
54+
55+
# Rationale and alternatives
56+
[rationale-and-alternatives]: #rationale-and-alternatives
57+
58+
The use of the `#[diagnostic]` namespace has a few benefits:
59+
60+
- Any library with an MSRV >= 1.78 can safely add the annotation to their APIs without concerns about their users seeing warnings or errors
61+
- We don't pollute the top-level namespace with yet another marker
62+
- The namespace is explicitly allowing for `rustc` to do *nothing* with this information. These `blocking` markers are useful even with a lack of tools using them, as they act as machine parseable documentation.
63+
- `clippy` (and `rustc`) can maintain a master list of paths to check for, but such lists are usually incomplete and always unwhieldy to maintain.
64+
- `clippy` could add their own attribute, but this is such a fundamental signal that I believe *should* live in the language and not in external tools.
65+
- We could add a `#[rustc_blocking]` attribute, but then third party crates would not be able to use these.
66+
67+
# Prior art
68+
[prior-art]: #prior-art
69+
70+
`rustc` currently has a number of `#[rustc_*]` attributes used to influence errors and lints. The language also has attributes like `#[must_use]` which are similar in spirit to the proposed attribute. `clippy` also has explicit checks for type paths of both `std` and third party crates types in order to lint against specific invalid uses.
71+
72+
# Unresolved questions
73+
[unresolved-questions]: #unresolved-questions
74+
75+
- What parts of the design do you expect to resolve through the RFC process before this gets merged?
76+
- What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
77+
- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?
78+
79+
# Future possibilities
80+
[future-possibilities]: #future-possibilities
81+
82+
If `rustc` ever gains the ability to perform call-graph analysis, an exhaustive check of all `async` functions for transitive reachability of blocking operations would provide perfect visibility into issues.

0 commit comments

Comments
 (0)