3 ms·
I'm possibly misunderstanding, but their first example doesn't make sense to me: 1. C can make aggressive aliasing optimizations based on observable behavior,
by woodruffw 3y ago
I'm possibly misunderstanding, but their first example doesn't make sense to me:
1. C can make aggressive aliasing optimizations based on observable behavior, meaning that the compiler may or may not optimize `add_twice` based on the results of any alias analysis it chooses to do. In other words: the author's assumption that a C compiler won't optimize `add_twice` in the same way that Rust will is not a guarantee, and assuming that it won't is relying on unspecified behavior.
2. The author claims that the result of the optimization can result in memory unsafety, and it's not immediately clear to me that that's true: it might be true in an interprocedural sense due to another function's assumptions about how `add_twice` affects its parameters, but this is the same incorrect assumption as in (1). In that sense, Rust is not really involved at all here.
- pornel 3y agoIn Rust mutable references are exclusive. They're statically guaranteed to never alias with any other pointer. However, when you call such function from C, the C compiler has no clue about exclusive references, so obviously it can't enforce their invariants. The C side can provide aliased pointers and break Rust's assumptions. The Rust side assumes it's called correctly, and the C side can't know what is correct.
- woodruffw 3y agoRight, but that's not controversial, is it? If you call into Rust from C, you need to preserve Rust's invariants; Rust can't preserve them for you.
- pornel 3y agoI don't think anybody expects incorrect code to behave correctly, but as usual the issue is about mistakes. The C compiler can't help you preserve Rust's invariants, so when you make wrong assumptions or declare a wrong interface, instead of a compilation error, you can get UB silently creeping in. The FFI-glue language they propose is meant to prevent these types of mistakes.
- wahern 3y ago> In other words: the author's assumption that a C compiler won't optimize `add_twice` in the same way that Rust will is not a guarantee, and assuming that it won't is relying on unspecified behavior. In C, objects of the same type are presumed to alias unless the compiler can prove otherwise. Unless add_twice is compiled as a file-scoped (i.e. static) function or with equivalent compiler-specific options, the compiler cannot prove that a and b don't alias, and therefore it cannot make such an optimization. This is why C99 added the "restrict" keyword, to tell the compiler that two parameters of the same type do not alias. This is indirectly why type punning is problematic in C--because C is pessimistic about aliasing, at least for objects of the same type, you normally can't run afoul of aliasing issues as the compiler bares the burden of ensuring safe behavior. But for objects of different types, the compiler can assume they don't alias. When type punning, at the point of usage it appears to the programmer and the compiler that they might alias (because they're the same type), but at some point earlier or later when operating on the original type of the type-punned object(s), the compiler can assume lack of aliasing, generating optimizations that at a distance break the type-safe code. Which is precisely the issue here with Rust FFI, and almost for the same reason--conflict between different aliasing rules in different contexts as Rust presumes lack of aliasing even for objects of the same type. In effect, with Rust FFI you're always type punning arguments passed into a Rust function, which is absolutely a noteworthy issue that absolutely should be addressed by adjusting the default semantics Rust applies to extern'd functions. (C isn't the only language that could run afoul of such aggressive semantics for foreign functions.)