Similar to our previous announcements of the Rust Project's participation in Google Summer of Code (GSoC), we are now announcing our participation in Open Source Promotion Plan (OSPP) 2024.
OSPP is a program organized in large part by The Institute of Software Chinese Academy of Sciences. Its goal is to encourage college students to participate in developing and maintaining open source software. The Rust Project is already registered and has a number of projects available for mentorship:
Eligibility is limited to students and there is a guide for potential participants. Student registration ends on the 3rd of June with the project application deadline a day later.
Unlike GSoC which allows students to propose their own projects, OSPP requires that students only apply for one of the registered projects. We do have an #ospp Zulip stream and potential contributors are encouraged to join and discuss details about the projects and connect with mentors.
After the project application window closes on June 4th, we will review and select participants, which will be announced on June 26th. From there, students will participate through to the end of September.
As with GSoC, this is our first year participating in this program. We are incredibly excited for this opportunity to further expand into new open source communities and we're hopeful for a productive and educational summer.
The Cargo and Compiler team are delighted to announce that starting with Rust 1.80 (or nightly-2024-05-05) every reachable #[cfg]
will be automatically checked that they match the expected config names and values.
This can help with verifying that the crate is correctly handling conditional compilation for different target platforms or features. It ensures that the cfg settings are consistent between what is intended and what is used, helping to catch potential bugs or errors early in the development process.
This addresses a common pitfall for new and advanced users.
This is another step to our commitment to provide user-focused tooling and we are eager and excited to finally see it fixed, after more than two years since the original RFC 30131.
Every time a Cargo feature is declared that feature is transformed into a config that is passed to rustc
(the Rust compiler) so it can verify with it along with well known cfgs if any of the #[cfg]
, #![cfg_attr]
and cfg!
have unexpected configs and report a warning with the unexpected_cfgs
lint.
Cargo.toml
:
[package]
name = "foo"
[features]
lasers = []
zapping = []
src/lib.rs
:
#[cfg(feature = "lasers")] // This condition is expected
// as "lasers" is an expected value
// of the `feature` cfg
fn shoot_lasers() {}
#[cfg(feature = "monkeys")] // This condition is UNEXPECTED
// as "monkeys" is NOT an expected
// value of the `feature` cfg
fn write_shakespeare() {}
#[cfg(windosw)] // This condition is UNEXPECTED
// it's supposed to be `windows`
fn win() {}
cargo check
:
In Cargo point-of-view: a custom cfg is one that is neither defined by
rustc
nor by a Cargo feature. Think oftokio_unstable
,has_foo
, ... but notfeature = "lasers"
,unix
ordebug_assertions
Some crates use custom cfgs that they either expected from the environment (RUSTFLAGS
or other means) or is enabled by some logic in the crate build.rs
. For those crates Cargo provides a new instruction: cargo::rustc-check-cfg2 (or cargo:rustc-check-cfg
for older Cargo version).
The syntax to use is described in the rustc book section checking configuration, but in a nutshell the basic syntax of --check-cfg
is:
cfg(name, values("value1", "value2", ..., "valueN"))
Note that every custom cfgs must always be expected, regardless if the cfg is active or not!
build.rs
examplebuild.rs
:
fn main() {
println!("cargo::rustc-check-cfg=cfg(has_foo)");
// ^^^^^^^^^^^^^^^^^^^^^^ new with Cargo 1.80
if has_foo() {
println!("cargo::rustc-cfg=has_foo");
}
}
Each
cargo::rustc-cfg
should have an accompanying unconditionalcargo::rustc-check-cfg
directive to avoid warnings like this:unexpected cfg condition name: has_foo
.
cargo::rustc-cfg
cargo::rustc-check-cfg
foo
cfg(foo)
or cfg(foo, values(none()))
foo=""
cfg(foo, values(""))
foo="bar"
cfg(foo, values("bar"))
foo="1"
and foo="2"
cfg(foo, values("1", "2"))
foo="1"
and bar="2"
cfg(foo, values("1"))
and cfg(bar, values("2"))
foo
and foo="bar"
cfg(foo, values(none(), "bar"))
More details can be found in the rustc book.
For Cargo users, the feature is always on and cannot be disabled, but like any other lints it can be controlled: #![warn(unexpected_cfgs)]
.
No, like most lints, unexpected_cfgs
will only be reported for local packages thanks to cap-lints.
RUSTFLAGS
env?You should be able to use the RUSTFLAGS
environment variable like it was before.Currently --cfg
arguments are not checked, only usage in code are.
This means that doing RUSTFLAGS="--cfg tokio_unstable" cargo check
will not report any warnings, unless tokio_unstable
is used within your local crates, in which case crate author will need to make sure that that custom cfg is expected with cargo::rustc-check-cfg
in the build.rs
of that crate.
build.rs
?There is currently no way to expect a custom cfg other than with cargo::rustc-check-cfg
in a build.rs
.
Crate authors that don't want to use a build.rs
are encouraged to use Cargo features instead.
Non-Cargo based build systems are not affected by the lint by default. Build system authors that wish to have the same functionality should look at the rustc
documentation for the --check-cfg flag for a detailed explanation of how to achieve the same functionality.
--check-cfg
: cfg()
(instead of values()
and names()
being incomplete and subtlety incompatible with each other). ↩cargo::rustc-check-cfg
will start working in Rust 1.80 (or nightly-2024-05-05). From Rust 1.77 to Rust 1.79 (inclusive) it is silently ignored. In Rust 1.76 and below a warning is emitted when used without the unstable Cargo flag -Zcheck-cfg
. ↩The Rustup team is happy to announce the release of Rustup version 1.27.1.Rustup is the recommended tool to install Rust, a programming language that is empowering everyone to build reliable and efficient software.
If you have a previous version of Rustup installed, getting Rustup 1.27.1 is as easy as stopping any programs which may be using Rustup (e.g. closing your IDE) and running:
$ rustup self update
Rustup will also automatically update itself at the end of a normal toolchain update:
$ rustup update
If you don't have it already, you can get Rustup from the appropriate page on our website.
This new Rustup release involves some minor bug fixes.
The headlines for this release are:
rustup-init
will no longer fail when fish
is installed but ~/.config/fish/conf.d
hasn't been created.RUSTUP_HOME/(toolchains|downloads|tmp)
have been addressed.Full details are available in the changelog!
Rustup's documentation is also available in the Rustup Book.
Thanks again to all the contributors who made Rustup 1.27.1 possible!
The Rust team is happy to announce a new version of Rust, 1.78.0. Rust is a programming language empowering everyone to build reliable and efficient software.
If you have a previous version of Rust installed via rustup
, you can get 1.78.0 with:
$ rustup update stable
If you don't have it already, you can get rustup from the appropriate page on our website, and check out the detailed release notes for 1.78.0.
If you'd like to help us out by testing future releases, you might consider updating locally to use the beta channel (rustup default beta
) or the nightly channel (rustup default nightly
). Please report any bugs you might come across!
Rust now supports a #[diagnostic]
attribute namespace to influence compiler error messages. These are treated as hints which the compiler is not required to use, and it is also not an error to provide a diagnostic that the compiler doesn't recognize. This flexibility allows source code to provide diagnostics even when they're not supported by all compilers, whether those are different versions or entirely different implementations.
With this namespace comes the first supported attribute, #[diagnostic::on_unimplemented]
, which can be placed on a trait to customize the message when that trait is required but hasn't been implemented on a type. Consider the example given in the stabilization pull request:
#[diagnostic::on_unimplemented(
message = "My Message for `ImportantTrait<{A}>` is not implemented for `{Self}`",
label = "My Label",
note = "Note 1",
note = "Note 2"
)]
trait ImportantTrait<A> {}
fn use_my_trait(_: impl ImportantTrait<i32>) {}
fn main() {
use_my_trait(String::new());
}
Previously, the compiler would give a builtin error like this:
error[E0277]: the trait bound `String: ImportantTrait<i32>` is not satisfied
--> src/main.rs:12:18
|
12 | use_my_trait(String::new());
| ------------ ^^^^^^^^^^^^^ the trait `ImportantTrait<i32>` is not implemented for `String`
| |
| required by a bound introduced by this call
|
With #[diagnostic::on_unimplemented]
, its custom message fills the primary error line, and its custom label is placed on the source output. The original label is still written as help output, and any custom notes are written as well. (These exact details are subject to change.)
error[E0277]: My Message for `ImportantTrait<i32>` is not implemented for `String`
--> src/main.rs:12:18
|
12 | use_my_trait(String::new());
| ------------ ^^^^^^^^^^^^^ My Label
| |
| required by a bound introduced by this call
|
= help: the trait `ImportantTrait<i32>` is not implemented for `String`
= note: Note 1
= note: Note 2
For trait authors, this kind of diagnostic is more useful if you can provide a better hint than just talking about the missing implementation itself. For example, this is an abridged sample from the standard library:
#[diagnostic::on_unimplemented(
message = "the size for values of type `{Self}` cannot be known at compilation time",
label = "doesn't have a size known at compile-time"
)]
pub trait Sized {}
For more information, see the reference section on the diagnostic tool attribute namespace.
unsafe
preconditionsThe Rust standard library has a number of assertions for the preconditions of unsafe
functions, but historically they have only been enabled in #[cfg(debug_assertions)]
builds of the standard library to avoid affecting release performance. However, since the standard library is usually compiled and distributed in release mode, most Rust developers weren't ever executing these checks at all.
Now, the condition for these assertions is delayed until code generation, so they will be checked depending on the user's own setting for debug assertions -- enabled by default in debug and test builds. This change helps users catch undefined behavior in their code, though the details of how much is checked are generally not stable.
For example, slice::from_raw_parts requires an aligned non-null pointer. The following use of a purposely-misaligned pointer has undefined behavior, and while if you were unlucky it may have appeared to "work" in the past, the debug assertion can now catch it:
fn main() {
let slice: &[u8] = &[1, 2, 3, 4, 5];
let ptr = slice.as_ptr();
// Create an offset from `ptr` that will always be one off from `u16`'s correct alignment
let i = usize::from(ptr as usize & 1 == 0);
let slice16: &[u16] = unsafe { std::slice::from_raw_parts(ptr.add(i).cast::<u16>(), 2) };
dbg!(slice16);
}
thread 'main' panicked at library/core/src/panicking.rs:220:5:
unsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread caused non-unwinding panic. aborting.
The standard library has a few functions that change the alignment of pointers and slices, but they previously had caveats that made them difficult to rely on in practice, if you followed their documentation precisely. Those caveats primarily existed as a hedge against const
evaluation, but they're only stable for non-const
use anyway. They are now promised to have consistent runtime behavior according to their actual inputs.
usize::MAX
if that is not possible, but it was previously permitted to always return usize::MAX
, and now that behavior is removed.These APIs are now stable in const contexts:
x86_64-pc-windows-msvc
i686-pc-windows-msvc
x86_64-pc-windows-gnu
i686-pc-windows-gnu
x86_64-pc-windows-gnullvm
i686-pc-windows-gnullvm
Check out everything that changed in Rust, Cargo, and Clippy.
Many people came together to create Rust 1.78.0. We couldn't have done it without all of you. Thanks!
The Rust Project is participating in Google Summer of Code (GSoC) 2024, a global program organized by Google which is designed to bring new contributors to the world of open-source.
In February, we published a list of GSoC project ideas, and started discussing these projects with potential GSoC applicants on our Zulip. We were pleasantly surprised by the amount of people that wanted to participate in these projects and that led to many fruitful discussions with members of various Rust teams. Some of them even immediately began contributing to various repositories of the Rust Project, even before GSoC officially started!
After the initial discussions, GSoC applicants prepared and submitted their project proposals. We received 65 (!) proposals in total. We are happy to see that there was so much interest, given that this is the first time the Rust Project is participating in GSoC.
A team of mentors primarily composed of Rust Project contributors then thoroughly examined the submitted proposals. GSoC required us to produce a ranked list of the best proposals, which was a challenging task in itself since Rust is a big project with many priorities! We went through many rounds of discussions and had to consider many factors, such as prior conversations with the given applicant, the quality and scope of their proposal, the importance of the proposed project for the Rust Project and its wider community, but also the availability of mentors, who are often volunteers and thus have limited time available for mentoring.
In many cases, we had multiple proposals that aimed to accomplish the same goal. Therefore, we had to pick only one per project topic despite receiving several high-quality proposals from people we'd love to work with. We also often had to choose between great proposals targeting different work within the same Rust component to avoid overloading a single mentor with multiple projects.
In the end, we narrowed the list down to twelve best proposals, which we felt was the maximum amount that we could realistically support with our available mentor pool. We submitted this list and eagerly awaited how many of these twelve proposals would be accepted into GSoC.
On the 1st of May, Google has announced the accepted projects. We are happy to announce that 9
proposals out of the twelve that we have submitted were accepted by Google, and will thus participate in Google Summer of Code 2024! Below you can find the list of accepted proposals (in alphabetical order), along with the names of their authors and the assigned mentor(s):
Congratulations to all applicants whose project was selected! The mentors are looking forward to working with you on these exciting projects to improve the Rust ecosystem. You can expect to hear from us soon, so that we can start coordinating the work on your GSoC projects.
We would also like to thank all the applicants whose proposal was sadly not accepted, for their interactions with the Rust community and contributions to various Rust projects. There were some great proposals that did not make the cut, in large part because of limited review capacity. However, even if your proposal was not accepted, we would be happy if you would consider contributing to the projects that got you interested, even outside GSoC! Our project idea list is still actual, and could serve as a general entry point for contributors that would like to work on projects that would help the Rust Project maintainers and the Rust ecosystem.
Assuming our involvement in GSoC 2024 is successful, there's a good chance we'll participate next year as well (though we can't promise anything yet) and we hope to receive your proposals again in the future! We also are planning to participate in similar programs in the very near future. Those announcements will come in separate blog posts, so make sure to subscribe to this blog so that you don't miss anything.
The accepted GSoC projects will run for several months. After GSoC 2024 finishes (in autumn of 2024), we plan to publish a blog post in which we will summarize the outcome of the accepted projects.
The Rust Security Response WG was notified that the Rust standard library did not properly escape arguments when invoking batch files (with the bat
andcmd
extensions) on Windows using the Command API. An attacker able to control the arguments passed to the spawned process could execute arbitrary shell commands by bypassing the escaping.
The severity of this vulnerability is critical if you are invoking batch files on Windows with untrusted arguments. No other platform or use is affected.
This vulnerability is identified by CVE-2024-24576.
The Command::arg and Command::args APIs state in their documentation that the arguments will be passed to the spawned process as-is, regardless of the content of the arguments, and will not be evaluated by a shell. This means it should be safe to pass untrusted input as an argument.
On Windows, the implementation of this is more complex than other platforms, because the Windows API only provides a single string containing all the arguments to the spawned process, and it's up to the spawned process to split them. Most programs use the standard C run-time argv, which in practice results in a mostly consistent way arguments are splitted.
One exception though is cmd.exe
(used among other things to execute batch files), which has its own argument splitting logic. That forces the standard library to implement custom escaping for arguments passed to batch files. Unfortunately it was reported that our escaping logic was not thorough enough, and it was possible to pass malicious arguments that would result in arbitrary shell execution.
Due to the complexity of cmd.exe
, we didn't identify a solution that would correctly escape arguments in all cases. To maintain our API guarantees, we improved the robustness of the escaping code, and changed the Command
API to return an InvalidInput error when it cannot safely escape an argument. This error will be emitted when spawning the process.
The fix will be included in Rust 1.77.2, to be released later today.
If you implement the escaping yourself or only handle trusted inputs, on Windows you can also use the CommandExt::raw_arg method to bypass the standard library's escaping logic.
All Rust versions before 1.77.2 on Windows are affected, if your code or one of your dependencies executes batch files with untrusted arguments. Other platforms or other uses on Windows are not affected.
We want to thank RyotaK for responsibly disclosing this to us according to theRust security policy, and Simon Sawicki (Grub4K) for identifying some of the escaping rules we adopted in our fix.
We also want to thank the members of the Rust project who helped us disclose the vulnerability: Chris Denton for developing the fix; Mara Bos for reviewing the fix; Pietro Albini for writing this advisory; Pietro Albini, Manish Goregaokar and Josh Stone for coordinating this disclosure; Amanieu d'Antras for advising during the disclosure.
The Rust team has published a new point release of Rust, 1.77.2. Rust is a programming language that is empowering everyone to build reliable and efficient software.
If you have a previous version of Rust installed via rustup, getting Rust 1.77.2 is as easy as:
rustup update stable
If you don't have it already, you can get rustup from the appropriate page on our website.
This release includes a fix for CVE-2024-24576.
Before this release, the Rust standard library did not properly escape arguments when invoking batch files (with the bat
and cmd
extensions) on Windows using the Command API. An attacker able to control the arguments passed to the spawned process could execute arbitrary shell commands by bypassing the escaping.
This vulnerability is CRITICAL if you are invoking batch files on Windows with untrusted arguments. No other platform or use is affected.
You can learn more about the vulnerability in the dedicated advisory.
Many people came together to create Rust 1.77.2. We couldn't have done it without all of you. Thanks!
WASI 0.2 was recently stabilized, and Rust has begun implementing first-class support for it in the form of a dedicated new target. Rust 1.78 will introduce new wasm32-wasip1
(tier 2) and wasm32-wasip2
(tier 3) targets. wasm32-wasip1
is an effective rename of the existing wasm32-wasi
target, freeing the target name up for an eventual WASI 1.0 release. Starting Rust 1.78 (May 2nd, 2024), users of WASI 0.1 are encouraged to begin migrating to the new wasm32-wasip1
target before the existing wasm32-wasi
target is removed in Rust 1.84 (January 5th, 2025).
In this post we'll discuss the introduction of the new targets, the motivation behind it, what that means for the existing WASI targets, and a detailed schedule for these changes. This post is about the WASI targets only; the existing wasm32-unknown-unknown
and wasm32-unknown-emscripten
targets are unaffected by any changes in this post.
wasm32-wasip2
After nearly five years of work the WASI 0.2 specificationwas recently stabilized. This work builds on WebAssembly Components (think: strongly-typed ABI for Wasm), providing standard interfaces for things like asynchronous IO, networking, and HTTP. This will finally make it possible to write asynchronous networked services on top of WASI, something which wasn't possible using WASI 0.1.
People interested in compiling Rust code to WASI 0.2 today are able to do so using the cargo-componenttool. This tool is able to take WASI 0.1 binaries, and transform them to WASI 0.2 Components using a shim. It also provides native support for common cargo commands such as cargo build
, cargo test
, and cargo run
. While it introduces some inefficiencies because of the additional translation layer, in practice this already works really well and people should be enough able to get started with WASI 0.2 development.
We're however keen to begin making that translation layer obsolete. And for that reason we're happy to share that Rust has made its first steps towards that with the introduction of the tier 3 wasm32-wasip2
target landing in Rust 1.78. This will initially miss a lot of expected features such as stdlib support, and we don't recommend people use this target quite yet. But as we fill in those missing features over the coming months, we aim to eventually hit meet the criteria to become a tier 2 target, at which point the wasm32-wasip2
target would be considered ready for general use. This work will happen through 2024, and we expect for this to land before the end of the calendar year.
wasm32-wasi
to wasm32-wasip1
The original name for what we now call WASI 0.1 was "WebAssembly System Interface, snapshot 1". Rust shipped support for this in 2019, and we did so knowing the target would likely undergo significant changes in the future. With the knowledge we have today though, we would not have chosen to introduce the "WASI, snapshot 1" target as wasm32-wasi
. We should have instead chosen to add some suffix to the initial target triple so that the eventual stable WASI 1.0 target can just be called wasm32-wasi
.
In anticipation of both an eventual WASI 1.0 target, and to preserve consistency between target names, we'll begin rolling out a name change to the existing WASI 0.1 target. Starting in Rust 1.78 (May 2nd, 2024) a new wasm32-wasip1
target will become available. Starting Rust 1.81 (September 5th, 2024) we will begin warning existing users of wasm32-wasi
to migrate to wasm32-wasip1
. And finally in Rust 1.84 (January 9th, 2025) the wasm32-wasi
target will no longer be shipped on the stable release channel. This will provide an 8 month transition period for projects to switch to the new target name when they update their Rust toolchains.
The name wasip1
can be read as either "WASI (zero) point one" or "WASI preview one". The official specification uses the "preview" moniker, however in most communication the form "WASI 0.1" is now preferred. This target triple was chosen because it not only maps to both terms, but also more closely resembles the target terminology used in other programming languages. This is something the WASI Preview 2 specification also makes note of.
This table provides the dates and cut-offs for the target rename fromwasm32-wasi
to wasm32-wasip1
. The dates in this table do not apply to the newly-introduced wasm32-wasi-preview1-threads
target; this will be renamed towasm32-wasip1-threads
in Rust 1.78 without going through a transition period. The tier 3 wasm32-wasip2
target will also be made available in Rust 1.78.
date
Rust Stable
Rust Beta
Rust Nightly
Notes
2024-02-08
1.76
1.77
1.78
wasm32-wasip1
available on nightly
2024-03-21
1.77
1.78
1.79
wasm32-wasip1
available on beta
2024-05-02
1.78
1.79
1.80
wasm32-wasip1
available on stable
2024-06-13
1.79
1.80
1.81
warn if wasm32-wasi
is used on nightly
2024-07-25
1.80
1.81
1.82
warn if wasm32-wasi
is used on beta
2024-09-05
1.81
1.82
1.83
warn if wasm32-wasi
is used on stable
2024-10-17
1.82
1.83
1.84
wasm32-wasi
unavailable on nightly
2024-11-28
1.83
1.84
1.85
wasm32-wasi
unavailable on beta
2025-01-09
1.84
1.85
1.86
wasm32-wasi
unavailable on stable
In this post we've discussed the upcoming updates to Rust's WASI targets. Come Rust 1.78 the wasm32-wasip1
(tier 2) and wasm32-wasip2
(tier 3) targets will be added. In Rust 1.81 we will begin warning if wasm32-wasi
is being used. And in Rust 1.84, the existing wasm32-wasi
target will be removed. This will free up wasm32-wasi
to eventually be used for a WASI 1.0 target. Users will have 8 months to switch to the new target name when they update their Rust toolchains.
The wasm32-wasip2
target marks the start of native support for WASI 0.2. In order to target it today from Rust, people are encouraged to usecargo-component tool instead. The plan is to eventually graduate wasm32-wasip2
to a tier-2 target, at which point cargo-component
will be upgraded to support it natively instead.
With WASI 0.2 finally stable, it's an exciting time for WebAssembly development. We're happy for Rust to begin implementing native support for WASI 0.2, and we're excited for what this will enable people to build.
Rust has long had an inconsistency with C regarding the alignment of 128-bit integers on the x86-32 and x86-64 architectures. This problem has recently been resolved, but the fix comes with some effects that are worth being aware of.
As a user, you most likely do not need to worry about these changes unless you are:
i128
/u128
rather than using align_of
improper_ctypes*
lints and using these types in FFIThere are also no changes to architectures other than x86-32 and x86-64. If your code makes heavy use of 128-bit integers, you may notice runtime performance increases at a possible cost of additional memory use.
This post documents what the problem was, what changed to fix it, and what to expect with the changes. If you are already familiar with the problem and only looking for a compatibility matrix, jump to the Compatibility section.
Data types have two intrinsic values that relate to how they can be arranged in memory; size and alignment. A type's size is the amount of space it takes up in memory, and its alignment specifies which addresses it is allowed to be placed at.
The size of simple types like primitives is usually unambiguous, being the exact size of the data they represent with no padding (unused space). For example, an i64
always has a size of 64 bits or 8 bytes.
Alignment, however, can vary. An 8-byte integer could be stored at any memory address (1-byte aligned), but most 64-bit computers will get the best performance if it is instead stored at a multiple of 8 (8-byte aligned). So, like in other languages, primitives in Rust have this most efficient alignment by default. The effects of this can be seen when creating composite types (playground link):
use core::mem::{align_of, offset_of};
#[repr(C)]
struct Foo {
a: u8, // 1-byte aligned
b: u16, // 2-byte aligned
}
#[repr(C)]
struct Bar {
a: u8, // 1-byte aligned
b: u64, // 8-byte aligned
}
println!("Offset of b (u16) in Foo: {}", offset_of!(Foo, b));
println!("Alignment of Foo: {}", align_of::<Foo>());
println!("Offset of b (u64) in Bar: {}", offset_of!(Bar, b));
println!("Alignment of Bar: {}", align_of::<Bar>());
Output:
Offset of b (u16) in Foo: 2
Alignment of Foo: 2
Offset of b (u64) in Bar: 8
Alignment of Bar: 8
We see that within a struct, a type will always be placed such that its offset is a multiple of its alignment - even if this means unused space (Rust minimizes this by default when repr(C)
is not used).
These numbers are not arbitrary; the application binary interface (ABI) says what they should be. In the x86-64 psABI (processor-specific ABI) for System V (Unix & Linux),Figure 3.1: Scalar Types tells us exactly how primitives should be represented:
C type
Rust equivalent
sizeof
Alignment (bytes)
char
i8
1
1
unsigned char
u8
1
1
short
i16
2
2
unsigned short
u16
2
2
long
i64
8
8
unsigned long
u64
8
8
The ABI only specifies C types, but Rust follows the same definitions both for compatibility and for the performance benefits.
If two implementations disagree on the alignment of a data type, they cannot reliably share data containing that type. Rust had inconsistent alignment for 128-bit types:
println!("alignment of i128: {}", align_of::<i128>());
// rustc 1.76.0
alignment of i128: 8
printf("alignment of __int128: %zu\n", _Alignof(__int128));
// gcc 13.2
alignment of __int128: 16
// clang 17.0.1
alignment of __int128: 16
(Godbolt link) Looking back at the psABI, we can see that Rust has the wrong alignment here:
C type
Rust equivalent
sizeof
Alignment (bytes)
__int128
i128
16
16
unsigned __int128
u128
16
16
It turns out this isn't because of something that Rust is actively doing incorrectly: layout of primitives comes from the LLVM codegen backend used by both Rust and Clang, among other languages, and it has the alignment for i128
hardcoded to 8 bytes.
Clang uses the correct alignment only because of a workaround, where the alignment is manually set to 16 bytes before handing the type to LLVM. This fixes the layout issue but has been the source of some other minor problems.2Rust does no such manual adjustement, hence the issue reported athttps://github.com/rust-lang/rust/issues/54341.
There is an additional problem: LLVM does not always do the correct thing when passing 128-bit integers as function arguments. This was a known issue in LLVM, before itsrelevance to Rust was discovered.
When calling a function, the arguments get passed in registers (special storage locations within the CPU) until there are no more slots, then they get "spilled" to the stack (the program's memory). The ABI tells us what to do here as well, in the section 3.2.3 Parameter Passing:
Arguments of type
__int128
offer the same operations as INTEGERs, yet they do not fit into one general purpose register but require two registers. For classification purposes__int128
is treated as if it were implemented as:typedef struct { long low, high; } __int128;
with the exception that arguments of type
__int128
that are stored in memory must be aligned on a 16-byte boundary.
We can try this out by implementing the calling convention manually. In the below C example, inline assembly is used to call foo(0xaf, val, val, val)
with val
as0x0x11223344556677889900aabbccddeeff
.
x86-64 uses the registers rdi
, rsi
, rdx
, rcx
, r8
, and r9
to pass function arguments, in that order (you guessed it, this is also in the ABI). Each register fits a word (64 bits), and anything that doesn't fit gets push
ed to the stack.
/* full example at <https://godbolt.org/z/5c8cb5cxs> */
/* to see the issue, we need a padding value to "mess up" argument alignment */
void foo(char pad, __int128 a, __int128 b, __int128 c) {
printf("%#x\n", pad & 0xff);
print_i128(a);
print_i128(b);
print_i128(c);
}
int main() {
asm(
/* load arguments that fit in registers */
"movl $0xaf, %edi \n\t" /* 1st slot (edi): padding char (`edi` is the
* same as `rdi`, just a smaller access size) */
"movq $0x9900aabbccddeeff, %rsi \n\t" /* 2rd slot (rsi): lower half of `a` */
"movq $0x1122334455667788, %rdx \n\t" /* 3nd slot (rdx): upper half of `a` */
"movq $0x9900aabbccddeeff, %rcx \n\t" /* 4th slot (rcx): lower half of `b` */
"movq $0x1122334455667788, %r8 \n\t" /* 5th slot (r8): upper half of `b` */
"movq $0xdeadbeef4c0ffee0, %r9 \n\t" /* 6th slot (r9): should be unused, but
* let's trick clang! */
/* reuse our stored registers to load the stack */
"pushq %rdx \n\t" /* upper half of `c` gets passed on the stack */
"pushq %rsi \n\t" /* lower half of `c` gets passed on the stack */
"call foo \n\t" /* call the function */
"addq $16, %rsp \n\t" /* reset the stack */
);
}
Running the above with GCC prints the following expected output:
0xaf
0x11223344556677889900aabbccddeeff
0x11223344556677889900aabbccddeeff
0x11223344556677889900aabbccddeeff
But running with Clang 17 prints:
0xaf
0x11223344556677889900aabbccddeeff
0x11223344556677889900aabbccddeeff
0x9900aabbccddeeffdeadbeef4c0ffee0
//^^^^^^^^^^^^^^^^ this should be the lower half
// ^^^^^^^^^^^^^^^^ look familiar?
Surprise!
This illustrates the second problem: LLVM expects an i128
to be passed half in a register and half on the stack when possible, but this is not allowed by the ABI.
Since the behavior comes from LLVM and has no reasonable workaround, this is a problem in both Clang and Rust.
Getting these problems resolved was a lengthy effort by many people, starting with a patch by compiler team member Simonas Kazlauskas in 2017: D28990. Unfortunately, this wound up reverted. It was later attempted again in D86310 by LLVM contributor Harald van Dijk, which is the version that finally landed in October 2023.
Around the same time, Nikita Popov fixed the calling convention issue with D158169. Both of these changes made it into LLVM 18, meaning all relevant ABI issues will be resolved in both Clang and Rust that use this version (Clang 18 and Rust 1.78 when using the bundled LLVM).
However, rustc
can also use the version of LLVM installed on the system rather than a bundled version, which may be older. To mitigate the chance of problems from differing alignment with the same rustc
version, a proposal was introduced to manually correct the alignment like Clang has been doing. This was implemented by Matthew Maurer in #11672.
Since these changes, Rust now produces the correct alignment:
println!("alignment of i128: {}", align_of::<i128>());
// rustc 1.77.0
alignment of i128: 16
As mentioned above, part of the reason for an ABI to specify the alignment of a datatype is because it is more efficient on that architecture. We actually got to see that firsthand: the initial performance run with the manual alignment change showed nontrivial improvements to compiler performance (which relies heavily on 128-bit integers to work with integer literals). The downside of increasing alignment is that composite types do not always fit together as nicely in memory, leading to an increase in usage. Unfortunately this meant some of the performance wins needed to be sacrificed to avoid an increased memory footprint.
The most imporant question is how compatibility changed as a result of these fixes. In short, i128
and u128
with Rust using LLVM 18 (the default version starting with 1.78) will be completely compatible with any version of GCC, as well as Clang 18 and above (released March 2024). All other combinations have some incompatible cases, which are summarized in the table below:
Compiler 1
Compiler 2
status
Rust ≥ 1.78 with bundled LLVM (18)
GCC (any version)
Fully compatible
Rust ≥ 1.78 with bundled LLVM (18)
Clang ≥ 18
Fully compatible
Rust ≥ 1.77 with LLVM ≥ 18
GCC (any version)
Fully compatible
Rust ≥ 1.77 with LLVM ≥ 18
Clang ≥ 18
Fully compatible
Rust ≥ 1.77 with LLVM ≥ 18
Clang < 18
Storage compatible, has calling bug
Rust ≥ 1.77 with LLVM < 18
GCC (any version)
Storage compatible, has calling bug
Rust ≥ 1.77 with LLVM < 18
Clang (any version)
Storage compatible, has calling bug
Rust < 1.773
GCC (any version)
Incompatible
Rust < 1.773
Clang (any version)
Incompatible
GCC (any version)
Clang ≥ 18
Fully compatible
GCC (any version)
Clang < 18
Storage compatible with calling bug
As mentioned in the introduction, most users will notice no effects of this change unless you are already doing something questionable with these types.
Starting with Rust 1.77, it will be reasonably safe to start experimenting with 128-bit integers in FFI, with some more certainty coming with the LLVM update in 1.78. There is ongoing discussion about lifting the lint in an upcoming version, but we want to be cautious and avoid introducing silent breakage for users whose Rust compiler may be built with an older LLVM.
The Rust team has published a new point release of Rust, 1.77.1. Rust is a programming language that is empowering everyone to build reliable and efficient software.
If you have a previous version of Rust installed via rustup, getting Rust 1.77.1 is as easy as:
rustup update stable
If you don't have it already, you can get rustup from the appropriate page on our website.
Cargo enabled stripping of debuginfo in release builds by defaultin Rust 1.77.0. However, due to a pre-existing issue, debuginfo stripping does not behave in the expected way on Windows with the MSVC toolchain.
Rust 1.77.1 therefore disables the new Cargo behavior on Windows for targets that use MSVC. There are no changes for other targets. We plan to eventually re-enable debuginfo stripping in release mode in a later Rust release.
Many people came together to create Rust 1.77.1. We couldn't have done it without all of you. Thanks!