Are there (type-based) strict aliasing rules that unsafe code must follow?

In C and C++ there are strict aliasing rules, aka -fstrict-aliasing, as documented here and here. Roughly speaking, these make it undefined behavior to read or write an object at a different type than it was declared.

Does (unsafe) Rust have a similar restriction? Looking through the Rustonomicon, I see mention of the reference "shared xor mutable" aliasing rules. I also see some discussion that you must not violate the invariants of a type, for example using transmute to write 0 to a NonNull type. But I don't see discussion of type-based aliasing rules like in C and C++.

As specific examples, is these unsafe code snippet sound? It reads and writes a Box<u32> object as a usize. (In C and C++ the equivalent would violate the strict aliasing rules.)

pub fn access_box_as_usize(x: &mut Box<u32>) {
    assert!(std::mem::size_of::<Box<u32>>() == std::mem::size_of::<usize>());
    let x: &mut usize = unsafe { std::mem::transmute(x) };
    let tmp = *x;
    *x = tmp;
}
1 Like

In general, raw pointers are allowed to alias.

You can't mutate something through a raw pointer if it originally came from a &T reference without going through some form of UnsafeCell, though.

2 Likes

No, there is nothing similar in Rust where e. g. the name of a struct or its fields would matter for whether or not you're allowed to transmute between them.

Note however that many types in Rust (every custom struct/enum/union that isn't repr(C) or repr(transparent)) have an unspecified layout, so that transmuting between different such types is UB.

The code example you present should be sound.

Rust does have lots of its own aliasing restriction around mutable and shared references and even pointers created using references, there's even some restrictions that aren't quite 100% known yet whether or not they're supposed to be part of the language (e. g. lots of rules w. r. t. the “stacked borrows” model).

2 Likes

Yeah, running the snippet through Miri on the playground doesn't bring up any complaints.

The snippet raises some suspicions because we are reinterpreting the box's internal *mut u32 pointer as an integer and storing it in tmp, but I can see how such code might appear in more obscure situations.

Rust does not have such a restriction. Memory itself is untyped; the UB comes from operations that you do. So a block of memory being zero, for example, isn't a problem until it's read as a NonZeroU32, say.

(With a bunch of complications like if there's a reference to that memory, the compiler is allowed to spuriously dereference the reference at any point to read it, so if you have a local of type NonZeroU32 I'm not sure there's a sound way to actually write all the memory behind it to a zero. But type punning via pointer casts is definitely allowed -- you can read an f64 through a u32 pointer without worry, for example.)

1 Like

Even if it tchnically is, it's bad practice. Transmuting a pointer type is code smell – it's dangerous and results in code that does not have clear intent, since the transmute-ability of two pointer types, P<T> and Q<U>, isn't in unequivocal correspondence with the pointed types T and U.

Why do you (think you) need this? Likely, there are much better ways of achieving what you are trying to do in a different way.

There are currently no clear rules about when casting an integer to a pointer is correct, so I find your code sketchy for that reason, but there definitely isn't anything wrong with reading the Box<u32> as an usize, or with creating the &mut usize in the first place (although pointer casts are generally recommended over transmutes).

2 Likes

Thanks all, this answers my question.

For those that are curious: this came up in a context where I'm moving large blocks of memory around, where the memory contains heterogeneous data. I wanted to move it around by usize/u64/etc access granularity.

Then use ptr::copy or ptr::copy_nonoverlapping from the standard library. That is guaranteed to work correctly and be optimized as much as possible, and it doesn't require you to perform fishy pointer casts either.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.