Stream: general

Topic: what happened to #[repr(C)]


Jake Goulding (Jan 22 2019 at 03:03, on Zulip):
#[repr(C)]
struct X;

#[repr(C)]
struct Y(String);

Neither of these raise any warnings. I swear at one point they did. What happened to the warnings?

RalfJ (Jan 22 2019 at 08:28, on Zulip):

@Jake Goulding are you sure? seems like 1.0 didn't warn either: https://godbolt.org/z/-3K_fL

rkruppe (Jan 22 2019 at 08:29, on Zulip):

Yeah, the lint only fires if such a type is used in a function signature imported from C (not even when you define a extern fn in Rust)

Jake Goulding (Jan 22 2019 at 14:11, on Zulip):

@RalfJ I'm never sure about anything.

@rkruppe :-(

Jake Goulding (Jan 22 2019 at 14:11, on Zulip):

thanks

Nicole Mazzuca (Jan 22 2019 at 20:04, on Zulip):

#[repr(C)] is useful if you want a specific layout, even if that layout is not technically compatible with C

Jake Goulding (Jan 22 2019 at 20:20, on Zulip):

Ugh, now I remember this argument (which I see, but feel it's missing the forest for the trees)

nagisa (Jan 22 2019 at 20:21, on Zulip):

Ditto about extern "C" functions...

Jake Goulding (Jan 22 2019 at 20:21, on Zulip):

How do I make the compiler help prevent me and random people asking questions on Stack Overflow from shooting themselves in the foot then?

nagisa (Jan 22 2019 at 20:21, on Zulip):

or extern "anything", really

Jake Goulding (Jan 22 2019 at 20:22, on Zulip):

Which is something that the Rust compiler in generally lauded for

nagisa (Jan 22 2019 at 20:23, on Zulip):

@Jake Goulding I do not understand how people come to any conclusion about layout of types which have no layout specified and fields of which are private?

Jake Goulding (Jan 22 2019 at 20:23, on Zulip):

Rather seems like there should have been #[repr(not-C-but-im-special)]

rkruppe (Jan 22 2019 at 20:23, on Zulip):

arguably yes, but it's far too late to introduce that and demand people use it

Jake Goulding (Jan 22 2019 at 20:24, on Zulip):

@nagisa I'm not sure I follow your argument.

Jake Goulding (Jan 22 2019 at 20:25, on Zulip):

I would expect that the majority of people who type extern "C" fn actually want to integrate that function with C (or something else using the C ABI). Thus if a type is present that cannot be validly put into such an ABI, I'd hope the compiler would tell me.

nagisa (Jan 22 2019 at 20:26, on Zulip):

@Jake Goulding What I’m trying to say, that something like extern "C" id(x: *mut MyReprCStructWithUndefinedInternalLayout) -> *mut MyReprCStructWithUndefinedInternalLayout is still very much valid code that is hard to get wrong. But since you don’t know the layout of that struct you cannot write an equivalent typedef in C, and that is fine.

nagisa (Jan 22 2019 at 20:26, on Zulip):

my question is, if people are trying to interface with this struct, how do they figure out what the C type definition should look like at all?

nagisa (Jan 22 2019 at 20:27, on Zulip):

that seems like an obvious first roadblock

nagisa (Jan 22 2019 at 20:27, on Zulip):

like you cannot just write typedef struct { field: String } MyReprCStructWithUndefinedLayout, right?

Jake Goulding (Jan 22 2019 at 20:28, on Zulip):

Here's the concrete case that made me ask. Someone had the moral equivalent of extern "C" fn foo() -> (TcpListener, TcpStream). The compiler allows this quite happily. This seems extremely poor.

Jake Goulding (Jan 22 2019 at 20:29, on Zulip):

now, if there was a lint that said "hey, you are returning a non-repr(C) struct, probs not what you want", that would have saved them (or we could ask them why they ignored the warning)

nagisa (Jan 22 2019 at 20:30, on Zulip):

right, and nobody can bind to that function either way, because you simply cannot declare TcpListener or TcpStream over FFI. Now the story is quite different for when you have your own non-repr(C) structs that contain entirely primitives and are used in such extern "C" functions.

Jake Goulding (Jan 22 2019 at 20:30, on Zulip):

@nagisa in this case, it's hard to tell because the code was reduced, but they were basically just passing it back to Rust later

extern int foo();

void example(void) {
    int x = foo();
    // ...
    bar(x);
}
Jake Goulding (Jan 22 2019 at 20:31, on Zulip):

So they had an "opaque handle" (that somehow worked, they claimed)

nagisa (Jan 22 2019 at 20:31, on Zulip):

I’m not sure how any sort of lint could have helped with this mistake.

Jake Goulding (Jan 22 2019 at 20:32, on Zulip):

I'm... speechless, I guess.

nagisa (Jan 22 2019 at 20:32, on Zulip):

Even if we had lint that said something about returning a non-repr(C) struct, there’s no way to lint against people having a mismatched declaration on the other side of FFI

Jake Goulding (Jan 22 2019 at 20:33, on Zulip):

Yes, there are always times where people can get it wrong, that's why it's a warning / suggestion

nagisa (Jan 22 2019 at 20:33, on Zulip):

(note that I’m not arguing against adding such a lint, I’m just trying to understand how such a mistake could’ve been arrived at at all)

Jake Goulding (Jan 22 2019 at 20:33, on Zulip):

rustc sez: "hey, it's literally impossible to access this type in the C ABI"

Jake Goulding (Jan 22 2019 at 20:33, on Zulip):

this sparks the brain

Jake Goulding (Jan 22 2019 at 20:34, on Zulip):

we have lints for far less "meaningful" mistakes (e.g. all the snake_case stuff that I love)

nagisa (Jan 22 2019 at 20:34, on Zulip):

hey, it's literally impossible to access this type in the C ABI

It is if the declaration and definition of the function matches (e.g. Rust code both declares and defines the same function with the same signature).

Jake Goulding (Jan 22 2019 at 20:35, on Zulip):

Unclear what you mean: It is ... possible? impossible?

nagisa (Jan 22 2019 at 20:36, on Zulip):

It is possible to have extern "C" function to work with non-repr(C) return types and arguments. All extern "C" changes is how the arguments and return types are passed around. As long as expectations on both the caller and callee side match up, everything will work well, and there are reasonable use-cases for doing so (e.g. dynamically loaded libraries).

nagisa (Jan 22 2019 at 20:37, on Zulip):

we lint against that, because the common use-case is to interface with another language, and dynamic loading is a secondary use-case.

nagisa (Jan 22 2019 at 20:37, on Zulip):

that is fine.

Jake Goulding (Jan 22 2019 at 20:37, on Zulip):

there are reasonable use-cases

I definitely am not arguing against that. That's what allow is for

Jake Goulding (Jan 22 2019 at 20:38, on Zulip):

we lint against that

Wait... what do we lint against?

nagisa (Jan 22 2019 at 20:38, on Zulip):

We lint against using non-repr(C) in function signatures.

Jake Goulding (Jan 22 2019 at 20:39, on Zulip):

we do not

extern "C" fn foo() -> String {
    String::new()
}
   Compiling playground v0.0.1 (/playground)
warning: function is never used: `foo`
 --> src/lib.rs:1:1
  |
1 | extern "C" fn foo() -> String {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: #[warn(dead_code)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 0.49s
nagisa (Jan 22 2019 at 20:40, on Zulip):

wait, what do we lint against then lol.

nagisa (Jan 22 2019 at 20:40, on Zulip):

/me rattles their head

Jake Goulding (Jan 22 2019 at 20:40, on Zulip):

And now we are cycling back to my original point :wink:

Jake Goulding (Jan 22 2019 at 20:40, on Zulip):

But thank you for pointing out that it it (was?) only on functions.

Jake Goulding (Jan 22 2019 at 20:41, on Zulip):

That is certainly a piece I was missing.

nagisa (Jan 22 2019 at 20:41, on Zulip):

we lint against extern "C" { fn foo(x: String); }

nagisa (Jan 22 2019 at 20:41, on Zulip):

which makes sense because we are about 100% sure this function is being imported across FFI.

nagisa (Jan 22 2019 at 20:42, on Zulip):

We should perhaps lint against defining functions like that, with perhaps a lint that is different from the current one.

Jake Goulding (Jan 22 2019 at 20:42, on Zulip):

the uncertainty with the existing lint would be host process functions in a DLL, presumably?

nagisa (Jan 22 2019 at 20:43, on Zulip):

I do not think we should lint against #[repr(C)] structs, though. Mostly because a programmer would notice this either way by trying to declare a compatible type on the other side of the FFI.

Jake Goulding (Jan 22 2019 at 20:43, on Zulip):

Yeah, I'd be cool with that line in the sand as well.

rkruppe (Jan 22 2019 at 20:44, on Zulip):

I agree. I have actually previously argued for linting improper_ctypes in Rust-defined #[no_mangle] extern "C" functions. There was an issue about it, let me find it.

Jake Goulding (Jan 22 2019 at 20:44, on Zulip):

although I wish that there had been the "other" repr that wasn't conflicted meaning

Jake Goulding (Jan 22 2019 at 20:45, on Zulip):

Clearly we need a repr(cdylib) analog ;-)

rkruppe (Jan 22 2019 at 20:45, on Zulip):

https://github.com/rust-lang/rust/issues/19834

nagisa (Jan 22 2019 at 20:46, on Zulip):

/me looks at their comment in the linked issue.

Jake Goulding (Jan 22 2019 at 20:46, on Zulip):

Some jerk named "nagisa" shot it down, I see.

Jake Goulding (Jan 22 2019 at 20:46, on Zulip):

:rolling_on_the_floor_laughing:

nagisa (Jan 22 2019 at 20:47, on Zulip):

Though it was exactly everybody’s position at the time: no-false-positives in rustc, rest in clippy

nagisa (Jan 22 2019 at 20:47, on Zulip):

not sure whether it has changed yet, but it quite likely that it hasn’t.

Jake Goulding (Jan 22 2019 at 20:47, on Zulip):

I'm going to take a screenshot of your comment here saying it should exist and just post it with no commentary.

Jake Goulding (Jan 22 2019 at 20:47, on Zulip):

no false positives seems overly strong.

Jake Goulding (Jan 22 2019 at 20:48, on Zulip):

But certainly a very low rate, absolutely.

nagisa (Jan 22 2019 at 20:48, on Zulip):

Right, because extern "C" { fn ... } is also not quite 100% false-positive free.

nagisa (Jan 22 2019 at 20:55, on Zulip):

@RalfJ dug up a https://github.com/rust-lang/rust/issues/36464

RalfJ (Jan 22 2019 at 20:58, on Zulip):

well you linked to it from the other one^^

nagisa (Jan 23 2019 at 14:34, on Zulip):

One way to pass semi-C, semi-Rust structures across C is this (I just thought of it)

#[repr(C)] struct That {
    foo: c_int,
    bar: c_banana,
    qux: String,
}

extern "C" { fn foo (that: *const That); }
struct that {
     int foo;
     banana bar;
     uint8_t rest[];
}
void foo(const struct that* that) { ... }
nagisa (Jan 23 2019 at 14:37, on Zulip):

seems very fine to me as long as nobody attempts to interpret rest in any way.

Peter (Feb 02 2019 at 01:20, on Zulip):

My opinion stated in that issue hasn't changed. Please lint on non C compatible types in extern "C" signatures.

Nicole Mazzuca (Feb 02 2019 at 21:11, on Zulip):

@Peter don't we?

Nicole Mazzuca (Feb 02 2019 at 21:11, on Zulip):

what issue?

Peter (Feb 02 2019 at 21:12, on Zulip):

https://github.com/rust-lang/rust/issues/36464

Nicole Mazzuca (Feb 02 2019 at 21:12, on Zulip):

oh, I see

Nicole Mazzuca (Feb 02 2019 at 21:12, on Zulip):

yeah I agree

Nicole Mazzuca (Feb 02 2019 at 21:22, on Zulip):

@nagisa you can also just do:

struct Foo {
  s: String,
  x: i32,
  y: i32,
}

extern "C" fn make_Foo() -> Box<Foo>;

extern "C" fn get_x(f: &mut Foo) -> &mut i32;
struct Foo;

struct Foo* make_Foo(void);
int* get_x(struct Foo const* f);
Last update: Nov 20 2019 at 11:30UTC