Pre-RFC solidifying repr(Rust)


#1

#2

‘astrategy’ should probably read ‘strategy’.

Apart from that, I don’t understand why this is a breaking change. Could you add an example of code that breaks?


#3

I think a repr(C) struct with an unsized field being disallowed now is the only breaking change. So code fails to compile only because of a lint, not because something actually changed.


#4

I see, though I’m a bit surprised it is allowed now – surely code generation for such a struct must fail currently?


#5

Rust will use all of the same alignment and padding rules as repr©, the only difference between repr© and repr(Rust) is the ordering of fields.

I’d refrain from giving this guarantee, C ABIs contain some weird cases, like different alignment of doubles inside and outside of structs on x86 Linux. I’m not sure Rust should necessarily repeat them.

#include <iostream>

struct S {
  int i;
  double d;
};

int main() {
  std::cout << sizeof(double) << ' ' << sizeof(S) << '\n';
  std::cout << alignof(double) << ' ' << alignof(S) << '\n';
}

we@we-kubuntu32:~$ ./a.out 
8 12
8 4

#6

different alignment of doubles inside and outside of structs on x86 Linu

While that is indeed surprising, does it actually cause any trouble in practice?

Note that we need to support such nonsense for repr(C) to actually work. The more we try to differ from C, the more we need to maintain.


#7

I’m not talking about implementing/maintaining now, just about being cautious and not guaranteeing equivalency of repr(Rust) and repr(C) with regard to padding and alignment until the full story with Rust ABI is clear. For example, if we want the struct layout to be the same on Linux and Windows, or to guarantee alignof(T) == alignof(Wrapper(T)), it already has to differ from C on one of the platforms.


#8

On unary types: “All structs that have a single positive-sized field of type T (?Sized or not) and no destructor can be safely transmuted between each-other, as well as T itself.”

Does this also imply that &T can be transmuted to &UnaryStruct<T>, even if the reference is pointing to a variable of type T? (this would be quite useful)

If yes, how does that interact with the point @petrochenkov is making in the case T=double?


#9

I don’t quite follow this paragraph:

A repr© struct cannot have a ?Sized field. … This of course implies tuples are not repr©, and their padding is usually unspecified.

What do tuples have to do with having a ?Sized field?

One concern I have is with disallowing repr(C) on DSTs: it’s fairly common in C code to use structs with a zero or one element fixed size array as the last field, whose closest translation to rust code would be a DST. repr(fixed) is not a great solution because a fixed layout does not imply a C-compatible layout.

What’s the downside to making repr(C) more permissive - as long as there’s a well defined C equivalent to a given rust type, repr(C) could work.

At the very least I’m against ignoring repr(C) and only issueing a warning: if it’s ignored it should most definitely be an error, as the code almost certainly interfaces with C code and depends on the types being layout compatible. Backwards compatibility means that old code should still work, not that it should compile but then have undefined behaviour!


#10

The tuple thing was based off the old coercions RFC, but tuples-have-a-dst-field has been rolled back in a later RFC. So that note is just erroneous now.


#11

This is not the case as of today, if you include a zero-element array of some type, it makes the struct have at least the alignment of that type.


#12

Also, do we want to lock ourselves in with same pointer representation of zero-sized and non-zero-sized types?


#13

Maybe this isn’t the right place to put this but can make interpreting allocated memory as [u8] UB? This would us to safely extend vectors without zeroing (and make IO more efficient). My rational is that the following is safe (on linux at least) and is indistinguishable from allocating without zeroing.

use std::fs::File;
use std::io::prelude::*;
use std::io::SeekFrom;

fn fake_uninitialized() -> Vec<u8> {
    let mut v = Vec::new();
    v.reserve(100);

    let mut tmp_buf = vec![0; 100];
    let mut mem = File::open("/proc/self/mem").unwrap();
    mem.seek(SeekFrom::Start(v.as_ptr() as u64)).unwrap();
    assert!(mem.read(&mut tmp_buf[..]).unwrap() == 100);
    v.extend(tmp_buf);
    v
}