-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
extern types #1861
extern types #1861
Changes from 1 commit
6085dc4
b2d8042
e6f7f93
083adad
2f77316
949c292
9f6d098
cf183e9
434b472
b3d5ba3
bce8a7b
156ed4b
caa048b
d76b608
a590767
9efddd3
263b7c9
e884a0b
4b971bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
- Feature Name: extern_types | ||
- Start Date: 2017-01-18 | ||
- RFC PR: | ||
- Rust Issue: | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Add an `extern type` syntax for declaring types which are opaque to Rust's type | ||
system. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
When interacting with external libraries we often need to be able to handle | ||
pointers to data that we don't know the size or layout of. | ||
|
||
In C it's possible to declare a type but not define it. These incomplete types | ||
can only be used behind pointers, a compilation error will result if the | ||
user tries to use them in such a way that the compiler would need to know their | ||
layout. | ||
|
||
In Rust, we don't have this feature. Instead, a couple of problematic hacks are | ||
used in its place. | ||
|
||
One is, we define the type as an uninhabited type. eg. | ||
|
||
enum MyFfiType {} | ||
|
||
Another is, we define the type with a private field and no methods to construct | ||
it. | ||
|
||
struct MyFfiType { | ||
_priv: (), | ||
} | ||
|
||
The point of both these constructions is to prevent the user from being able to | ||
create or deal directly with instances of | ||
the type. Niether of these types accurately reflect the reality of the | ||
situation. The first definition is logically problematic as it defines a type | ||
which can never exist. This means that references to the type can also - | ||
logically - never exist and raw pointers to the type are guaranteed to be | ||
invalid. The second definition says that the type is a ZST, that we can store | ||
it on the stack and that we can call `ptr::read`, `mem::size_of` etc. on it. | ||
None of this is of course valid. | ||
|
||
This RFC instead proposes a way to directly express that a type exists but is | ||
unknown to Rust. | ||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
Add a new kind of type declaration, an `extern type`: | ||
|
||
extern type Foo; | ||
|
||
These can also be declared inside an `extern` block: | ||
|
||
extern { | ||
type Foo; | ||
} | ||
|
||
These types are FFI-safe. They are also DSTs, meaning that they implement | ||
`?Sized`. Being DSTs, they cannot be kept on the stack and can only be | ||
accessed through pointers. | ||
|
||
In Rust, pointers to DSTs carry metadata about the object being pointed to. For | ||
strings and slices this is the length of the buffer, for trait objects this is | ||
the object's vtable. For extern types the metadata is simply `()`. This means | ||
that a pointer to an extern type is identical to a raw pointer. It also means | ||
that if we store an extern type at the end of a container (such as a struct or | ||
tuple) pointers to that container will also be identical to raw pointers | ||
(despite the container as a whole being unsized). This is useful to support a | ||
pattern found in some C APIs where structs are passed around which have | ||
arbitrary data appended to the end of them: eg. | ||
|
||
```rust | ||
extern type OpaqueTail; | ||
|
||
#[repr(C)] | ||
struct FfiStruct { | ||
data: u8, | ||
more_data: u32, | ||
tail: OpaqueTail, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear whether you are proposing that this should be accepted by the compiler or not. I think that this is not as essential as the rest of the proposal and I'd suggest that you remove this example and specify that such types can only be used as pointer and reference types. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm saying it should be accepted. This isn't really hard to implement - just make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This structure seems useless to me. Consider 3 cases where
Now, all of these are invalid or can’t be made work:
EDIT: only case where this could work is
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about: fn return_reference(&T) -> &FfiStruct {}
fn consume_reference(&FfiStruct) {} One of the main uses of this type would be to define opaque types with headers of known layouts such that they can be handled and have lifetimes managed as though they are Rust types, despite being defined in C++ land. As an example, in the gecko codebase there is a type struct nsAString {
const uint16_t* data;
uint32_t length;
uint32_t flags;
}; And has multiple subclasses, which may or may not add extra data after the above data, such as struct nsFixedString : public nsAString {
uint32_t capacity;
uint16_t* buffer;
}; In rust we can then define #[repr(C)]
struct nsAString {
data: *const u16,
length: u32,
flags: u32,
_rest: OpaqueTail
} And then we could take Currently in our rust bindings we're working around this limitation by defining There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This can work, but you’ll need to either know the full type of Where’s the point of having a reference to #[repr(C)]
struct nsAString {
data: *const u16,
length: u32,
flags: u32,
_rest: OpaqueTail
} over a reference to
So still, I’m not seeing the point of allowing such a thing, given in how many cases this cannot reasonably work in a FFI context. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nagisa There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @canndrew How? Namely, please describe how much memory would the caller need to allocate on the stack so it could produce a valid pointer to pass to the function? @Ericson2314 yes, I’ve seen it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nagisa: The entire point of this RFC is that no Rust code can possibly create such a value on the stack - it can only obtain pointers to such values, and only from foreign (or unsafe pointer-casting) code. It is meant almost exactly to mimic the C/C++ notion of an "incomplete type" - which cannot be allocated on the stack in C/C++ either, but can be pointed/referred to. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nagisa 32 bits on a 32 bit system, 64 bits on a 64 bit system? I'm not sure I understand the question. Edit: Oh I get it. Yes, what @eternaleye said. |
||
} | ||
``` | ||
|
||
# How We Teach This | ||
[how-we-teach-this]: #how-we-teach-this | ||
|
||
This should be taught in the foreign function interface chapter of the rust | ||
book in place of where it currently tells people to use uninhabited enums | ||
(ack!). | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
Very slight addition of complexity to the language. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
Not do this. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
Should we allow generic lifetime and type parameters on extern types? If so, | ||
how do they effect the type in terms of variance? | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This diverges somewhat from
extern fn
where its a definition and not declaration. For consistency IMHO declarations should stay constrained toextern
blocks, like below.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, of course. They're two different things.