-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Unsafe Extern Blocks #3484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Unsafe Extern Blocks #3484
Changes from 3 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
63441ea
add rfc text
Lokathor 726478f
Update text/0000-unsafe-extern-blocks.md
Lokathor 47949ec
per https://github.com/rust-lang/rfcs/pull/3484#issuecomment-1758275493
Lokathor 0637684
typo: missing "have"
Lokathor 3fa1a61
corrections from Zulip feedback
Lokathor 7754241
typo: remove "of"
Lokathor 2144ac3
Update text/0000-unsafe-extern-blocks.md
Lokathor 676383f
Update text/0000-unsafe-extern-blocks.md
Lokathor d5bb7db
Update text/0000-unsafe-extern-blocks.md
Lokathor 6dba902
Cleanup whitespace
traviscross 23f0acf
Improve wording of the drawback
traviscross 1cef026
Improve wording of where `safe` is allowed
traviscross 842bd55
Fix typo
traviscross 176d73f
Clarify extent of UB
traviscross 60631ce
Clarify what we're replacing in the Reference
traviscross fc53654
Add reference to Rust issue 46188
traviscross 5cc4cc3
Clarify that we will "eventually" lint
traviscross 4684d53
Unwrap lines
traviscross b423b2b
Lowercase "undefined behavior"
traviscross 09a088c
Address feedback and questions
traviscross ca7713c
Add alternative of fixing LLVM (if it is a fix)
traviscross efc671c
Clarify about fixing LLVM despite C
traviscross 2c106c3
Clarify about `unsafe_code` and edition migration
traviscross c1192da
Fix typo
traviscross 9f36a92
Remove issue 46188 as a motivation
traviscross c198396
Remove unused "prior art" section
traviscross 39795a0
Fix optionality of `safe`/`unsafe` in guide section
traviscross 3b6ae2b
Prepare RFC 3484 to be merged
traviscross 45590fe
Do some copyediting
traviscross File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
|
||
- Feature Name: `unsafe_extern` | ||
- Start Date: 2023-05-23 | ||
- 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 | ||
|
||
In Edition 2024 it is `unsafe` to declare an `extern` function or static, but external functions and statics *can* be safe to use after the initial declaration. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
Simply declaring extern items, even without ever using them, can cause Undefined Behavior. | ||
When performing cross-language compilation, attributes on one function declaration can flow to the foreign declaration elsewhere within LLVM and cause a miscompilation. | ||
In Rust we consider all sources of Undefined Behavior to be `unsafe`, and so we must make declaring extern blocks be `unsafe`. | ||
The up-side to this change is that in the new style it will be possible to declare an extern fn that's safe to call after the initial unsafe declaration. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
Rust can utilize functions and statics from foreign code that are provided during linking, though it is `unsafe` to do so. | ||
|
||
An `extern` block can be placed anywhere a function declaration could appear (generally at the top level of a module). | ||
* You can always write `unsafe extern { ... }`. | ||
* If the `unsafe_code` lint is denied or forbidden at a particular scope it will cause the `unsafe extern` block to be a compilation error within that scope. | ||
* On editions >= 2024, you must write all `extern` blocks as `unsafe extern`. | ||
* On editions < 2024, it is allowed to write an `extern` block *without* the `unsafe` keyword, but this generates a compatibility warning that you should use the `unsafe` keyword. | ||
|
||
Within an `extern` block is zero or more declarations of external functions and/or external static values. | ||
An extern function is declared with a `;` instead of a function body (similar to a method of a trait). | ||
An extern static value is also declared with a `;` instead of an expression (similar to an associated const of a trait). | ||
In both cases, the actual function body or value is provided by whatever external source (which is probably not even written in Rust). | ||
|
||
When an `extern` block is used (with or without `unsafe` in front of it), all declarations within that `extern` block should have the `unsafe` or `safe` keywords as part of their signature. | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
If one of the two keywords is not explicitly provided, the declaration is assumed to be `unsafe`. | ||
The `safe` keyword is a contextual keyword, only used within `extern` blocks. | ||
|
||
```rust | ||
unsafe extern { | ||
// sqrt (from libm) can be called with any `f64` | ||
pub safe fn sqrt(x: f64) -> f64; | ||
|
||
// strlen (from libc) requires a valid pointer, | ||
// so we mark it as being an unsafe fn | ||
pub unsafe fn strlen(p: *const c_char) -> usize; | ||
|
||
// this function doesn't say safe or unsafe, so it defaults to unsafe | ||
pub fn free(p: *mut core::ffi::c_void); | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
pub safe static IMPORTANT_BYTES: [u8; 256]; | ||
|
||
pub safe static LINES: SyncUnsafeCell<i32>; | ||
} | ||
``` | ||
|
||
`extern` blocks are `unsafe` because if the declaration doesn't match the actual external function, or the actual external data, then it causes compile time Undefined Behavior (UB). | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Once they are unsafely declared, a `safe` item can be used outside the `extern` block as if it were any other safe function or static value declared within rust. | ||
The unsafe obligation of ensuring that the correct items are being linked to is performed by the crate making the declaration, not the crate using of that declaration. | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Items declared as `unsafe` *must* still have a correctly matching signature at compile time, but they *also* some sort of additional obligation for correct usage at runtime. | ||
They can only be used within an `unsafe` block. | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
The grammar of the langauge is updated so that: | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
* Editions >= 2024 *must* prefix all `extern` blocks with `unsafe`. | ||
* Editions < 2024 *should* prefix `extern` blocks with `unsafe`, this is a warn-by-default compatibility lint when `unsafe` is missing. | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Replace the *Functions* and *Statics* sections with the following: | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Functions | ||
Functions within external blocks are declared in the same way as other Rust functions, with the exception that they must not have a body and are instead terminated by a semicolon. Patterns are not allowed in parameters, only IDENTIFIER or _ may be used. The function qualifiers `const`, `async`, and `extern` are not allowed. If the function is unsafe to call, then the function should use the `unsafe` qualifier. If the function is safe to call, then the function should use the `safe` qualifier (a contextual keyword). Functions that are not qualified as `unsafe` or `safe` are assumed to be `unsafe`. | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
If the function signature declared in Rust is incompatible with the function signature as declared in the foreign code it is Undefined Behavior to compile and link the code. | ||
|
||
Functions within external blocks may be called by Rust code, just like functions defined in Rust. The Rust compiler will automatically use the correct foreign ABI when making the call. | ||
|
||
When coerced to a function pointer, a function declared in an extern block has type | ||
```rust | ||
extern "abi" for<'l1, ..., 'lm> fn(A1, ..., An) -> R | ||
``` | ||
where `'l1`, ... `'lm` are its lifetime parameters, `A1`, ..., `An` are the declared types of its parameters and `R` is the declared return type. | ||
|
||
### Statics | ||
Statics within external blocks are declared in the same way as statics outside of external blocks, except that they do not have an expression initializing their value. If the static is unsafe to access, then the static should use the `unsafe` qualifier. If the static is safe to access (and immutable), then the static should use the `safe` qualifier (a contextual keyword). Statics that are not qualified as `unsafe` or `safe` are assumed to be `unsafe`. | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Extern statics can be either immutable or mutable just like statics outside of external blocks. An immutable static must be initialized before any Rust code is executed. It is not enough for the static to be initialized before Rust code reads from it. A mutable extern static is always `unsafe` to access, the same as a Rust mutable static. | ||
Lokathor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
* It is very unfortunate to have to essentially reverse the status quo. | ||
traviscross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* Hopefully, allowing people to safely call some foreign functions will make up for the churn caused by this change. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
Incorrect extern declarations can cause UB in current Rust, but we have no way to automatically check that all declarations are correct, nor is such a thing likely to be developed. Making the declarations `unsafe` so that programmers are aware of the dangers and can give extern blocks the attention they deserve is the minimum step. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
None we are aware of. | ||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
* Extern declarations are actually *always* unsafe and able to cause UB regardless of edition. This RFC doesn't have a specific answer on how to improve pre-2024 code. | ||
|
||
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
None are apparent at this time. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.