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
Tracking issue for RFC 1861: Extern types #43467
Comments
This is not explicitly mentioned in the RFC, but I'm assuming different instances of extern {
type A;
type B;
}
fn convert_ref(r: &A) -> &B { r } |
@jethrogb That's certainly the intention, yes. |
Relatedly, is deciding whether we want to call it EDIT: rust-lang/rfcs#2071 is also relevant here w.r.t. the connotations of |
Can we get a bullet for the panic vs DynSized debate? |
I've started working on this, and I have a working simple initial version (no generics, no DynSized). I've however noticed a slight usability issue. In FFI code, it's frequent for raw pointers to be initialized to null using Despite being unsized, extern types are used through thin pointers, so it should be possible to use It is still possible to cast an integer to an extern type pointer, but this is not as nice as just using the function designed for this. Also this can never be done in a generic context. extern {
type foo;
}
fn null_foo() -> *const foo {
0usize as *const foo
} Really we'd want is a new trait to distinguish types which use thin pointers. It would be implemented automatically for all sized types and extern types. Then the cast above would succeed whenever the type is bounded by this trait. Eg, the function fn null<T: ?Sized + Thin>() -> *const T {
0usize as *const T
} However there's a risk of more and more such traits creeping up, such as |
I think we can add extern types now and live with |
@SimonSapin yeah, it's definitely a minor concern for now. I do think this problem of not having a trait bound to express "this type may be unsized be must have a thin pointer" might crop up in other places though. |
Oh yeah, I agree we should solve that eventually too. I’m only saying we might not need to solve all of it before we ship any of it. |
I've pushed an initial implementation in #44295 |
Implement RFC 1861: Extern types A few notes : - Type parameters are not supported. This was an unresolved question from the RFC. It is not clear how useful this feature is, and how variance should be treated. This can be added in a future PR. - `size_of_val` / `align_of_val` can be called with extern types, and respectively return 0 and 1. This differs from the RFC, which specified that they should panic, but after discussion with @eddyb on IRC this seems like a better solution. If/when a `DynSized` trait is added, this will be disallowed statically. - Auto traits are not implemented by default, since the contents of extern types is unknown. This means extern types are `!Sync`, `!Send` and `!Freeze`. This seems like the correct behaviour to me. Manual `unsafe impl Sync for Foo` is still possible. - This PR allows extern type to be used as the tail of a struct, as described by the RFC : ```rust extern { type OpaqueTail; } #[repr(C)] struct FfiStruct { data: u8, more_data: u32, tail: OpaqueTail, } ``` However this is undesirable, as the alignment of `tail` is unknown (the current PR assumes an alignment of 1). Unfortunately we can't prevent it in the general case as the tail could be a type parameter : ```rust #[repr(C)] struct FfiStruct<T: ?Sized> { data: u8, more_data: u32, tail: T, } ``` Adding a `DynSized` trait would solve this as well, by requiring tail fields to be bound by it. - Despite being unsized, pointers to extern types are thin and can be casted from/to integers. However it is not possible to write a `null<T>() -> *const T` function which works with extern types, as I've explained here : #43467 (comment) - Trait objects cannot be built from extern types. I intend to support it eventually, although how this interacts with `DynSized`/`size_of_val` is still unclear. - The definition of `c_void` is unmodified
Implement RFC 1861: Extern types A few notes : - Type parameters are not supported. This was an unresolved question from the RFC. It is not clear how useful this feature is, and how variance should be treated. This can be added in a future PR. - `size_of_val` / `align_of_val` can be called with extern types, and respectively return 0 and 1. This differs from the RFC, which specified that they should panic, but after discussion with @eddyb on IRC this seems like a better solution. If/when a `DynSized` trait is added, this will be disallowed statically. - Auto traits are not implemented by default, since the contents of extern types is unknown. This means extern types are `!Sync`, `!Send` and `!Freeze`. This seems like the correct behaviour to me. Manual `unsafe impl Sync for Foo` is still possible. - This PR allows extern type to be used as the tail of a struct, as described by the RFC : ```rust extern { type OpaqueTail; } #[repr(C)] struct FfiStruct { data: u8, more_data: u32, tail: OpaqueTail, } ``` However this is undesirable, as the alignment of `tail` is unknown (the current PR assumes an alignment of 1). Unfortunately we can't prevent it in the general case as the tail could be a type parameter : ```rust #[repr(C)] struct FfiStruct<T: ?Sized> { data: u8, more_data: u32, tail: T, } ``` Adding a `DynSized` trait would solve this as well, by requiring tail fields to be bound by it. - Despite being unsized, pointers to extern types are thin and can be casted from/to integers. However it is not possible to write a `null<T>() -> *const T` function which works with extern types, as I've explained here : #43467 (comment) - Trait objects cannot be built from extern types. I intend to support it eventually, although how this interacts with `DynSized`/`size_of_val` is still unclear. - The definition of `c_void` is unmodified
Implement RFC 1861: Extern types A few notes : - Type parameters are not supported. This was an unresolved question from the RFC. It is not clear how useful this feature is, and how variance should be treated. This can be added in a future PR. - `size_of_val` / `align_of_val` can be called with extern types, and respectively return 0 and 1. This differs from the RFC, which specified that they should panic, but after discussion with @eddyb on IRC this seems like a better solution. If/when a `DynSized` trait is added, this will be disallowed statically. - Auto traits are not implemented by default, since the contents of extern types is unknown. This means extern types are `!Sync`, `!Send` and `!Freeze`. This seems like the correct behaviour to me. Manual `unsafe impl Sync for Foo` is still possible. - This PR allows extern type to be used as the tail of a struct, as described by the RFC : ```rust extern { type OpaqueTail; } #[repr(C)] struct FfiStruct { data: u8, more_data: u32, tail: OpaqueTail, } ``` However this is undesirable, as the alignment of `tail` is unknown (the current PR assumes an alignment of 1). Unfortunately we can't prevent it in the general case as the tail could be a type parameter : ```rust #[repr(C)] struct FfiStruct<T: ?Sized> { data: u8, more_data: u32, tail: T, } ``` Adding a `DynSized` trait would solve this as well, by requiring tail fields to be bound by it. - Despite being unsized, pointers to extern types are thin and can be casted from/to integers. However it is not possible to write a `null<T>() -> *const T` function which works with extern types, as I've explained here : #43467 (comment) - Trait objects cannot be built from extern types. I intend to support it eventually, although how this interacts with `DynSized`/`size_of_val` is still unclear. - The definition of `c_void` is unmodified
Implement RFC 1861: Extern types A few notes : - Type parameters are not supported. This was an unresolved question from the RFC. It is not clear how useful this feature is, and how variance should be treated. This can be added in a future PR. - `size_of_val` / `align_of_val` can be called with extern types, and respectively return 0 and 1. This differs from the RFC, which specified that they should panic, but after discussion with @eddyb on IRC this seems like a better solution. If/when a `DynSized` trait is added, this will be disallowed statically. - Auto traits are not implemented by default, since the contents of extern types is unknown. This means extern types are `!Sync`, `!Send` and `!Freeze`. This seems like the correct behaviour to me. Manual `unsafe impl Sync for Foo` is still possible. - This PR allows extern type to be used as the tail of a struct, as described by the RFC : ```rust extern { type OpaqueTail; } #[repr(C)] struct FfiStruct { data: u8, more_data: u32, tail: OpaqueTail, } ``` However this is undesirable, as the alignment of `tail` is unknown (the current PR assumes an alignment of 1). Unfortunately we can't prevent it in the general case as the tail could be a type parameter : ```rust #[repr(C)] struct FfiStruct<T: ?Sized> { data: u8, more_data: u32, tail: T, } ``` Adding a `DynSized` trait would solve this as well, by requiring tail fields to be bound by it. - Despite being unsized, pointers to extern types are thin and can be casted from/to integers. However it is not possible to write a `null<T>() -> *const T` function which works with extern types, as I've explained here : #43467 (comment) - Trait objects cannot be built from extern types. I intend to support it eventually, although how this interacts with `DynSized`/`size_of_val` is still unclear. - The definition of `c_void` is unmodified
Implement RFC 1861: Extern types A few notes : - Type parameters are not supported. This was an unresolved question from the RFC. It is not clear how useful this feature is, and how variance should be treated. This can be added in a future PR. - `size_of_val` / `align_of_val` can be called with extern types, and respectively return 0 and 1. This differs from the RFC, which specified that they should panic, but after discussion with @eddyb on IRC this seems like a better solution. If/when a `DynSized` trait is added, this will be disallowed statically. - Auto traits are not implemented by default, since the contents of extern types is unknown. This means extern types are `!Sync`, `!Send` and `!Freeze`. This seems like the correct behaviour to me. Manual `unsafe impl Sync for Foo` is still possible. - This PR allows extern type to be used as the tail of a struct, as described by the RFC : ```rust extern { type OpaqueTail; } #[repr(C)] struct FfiStruct { data: u8, more_data: u32, tail: OpaqueTail, } ``` However this is undesirable, as the alignment of `tail` is unknown (the current PR assumes an alignment of 1). Unfortunately we can't prevent it in the general case as the tail could be a type parameter : ```rust #[repr(C)] struct FfiStruct<T: ?Sized> { data: u8, more_data: u32, tail: T, } ``` Adding a `DynSized` trait would solve this as well, by requiring tail fields to be bound by it. - Despite being unsized, pointers to extern types are thin and can be casted from/to integers. However it is not possible to write a `null<T>() -> *const T` function which works with extern types, as I've explained here : #43467 (comment) - Trait objects cannot be built from extern types. I intend to support it eventually, although how this interacts with `DynSized`/`size_of_val` is still unclear. - The definition of `c_void` is unmodified
While it is possible for Sync, Send, UnwindSafe and RefUnwindSafe, doing Should Or is it possible to declare an extern type is safe-by-default, which opt-out instead of opt-in? extern {
#[unsafe_impl_all_auto_traits_by_default]
type Foo;
}
impl !Send for Foo {} |
@kennytm What's the usecase? The semantics of |
@eddyb Use case: Trying to see if it's possible to make CStr a thin DST. I don't see anything related to a cell in #44295? It is reported to LLVM as an |
@kennytm So with |
We discussed this in the lang team backlog bonanza today. We'd like to make progress on this one, but were unsure exactly where things stood related to these around the big questions like whether pointers are thin and ptr::null works (did https://rust-lang.github.io/rfcs/2580-ptr-meta.html change anything here?), whether Is anyone interested here in summarizing the state of things? |
I didn’t realize it was a question whether pointers to extern types should be thin. What metadata would there be? RFC 2580 was accepted saying that That change in |
I am not sure if this relates to the current iteration of the question, but discussions about custom DSTs in the past had
Please, let's cross the implicit bound rubicon! |
I think custom metadata would be compatible with extending As to implicit bounds I’m only talking about the existing ones: fn null<T>() -> *const T {
0 as *const T
} |
Ah, yours does work today, but fn null<T: ?Sized>() -> *const T {
0 as *const T
} is does not. Great! That means the |
This is the tracking issue for |
#94954 implements this part of the pointer metadata RFC |
I haven't been involved in this so far but I'm going to attempt to summarize where this is in the hope that it allows some forward progress. The original RFC kind of relied on the custom DSTs RFC which has been postponed, this means some of the workarounds mentioned may need to be made more permanent for this to be stabilised. Extern types have been implemented in #44295, this also flagged some questions not covered by the RFC. The other relevant piece of work is the pointer metadata RFC (tracking issue). Some of this is copied from @jethrogb's summary above: #43467 (comment) Should we allow generic lifetime and type parameters on extern types?This appears to be entirely resolved by wrapping the extern type in a That does assume you can put extern types in transparent structs, see later, if not then:
What LLVM type should be used?Extern types currently use std::ptr::null/null_mutThese functions did not work as T had to be sized, the pointer metadata RFC weakened this to
|
Nice summary! Interestingly, if we had |
It sounded to me like there was some interest from the lang team on trying out |
I don't quite understand why giving extern types alignment 1/no alignment is wrong? It may be that the type Building upon your example, playground link (which doesn't compile).: #[derive(Default, Debug)]
#[repr(C)]
struct Bar {
data: u8,
// 8 bits of padding here
x: u16,
}
fn main() {
let bar = Bar::default();
let foo: &Foo<OpaqueTail> = unsafe { mem::transmute(&bar) };
// Possible, but would just point to the padding, and effectively useless
let tail = foo_tail(foo);
// Not possible, since `OpaqueTail` doesn't implement `Debug`
hidden(foo);
} Note that Related, I opened a proposal over at the Unsafe Code Guidelines to change how extern types works wrt. references: rust-lang/unsafe-code-guidelines#356 |
Hopefully this playground demonstrates the issue: Edit: Alternatively, here's a minor alteration that segfaults! |
Yeah, though I don't think this is an issue since this kind of API doesn't exist in the real world? Or am I wrong here? I should think My point is, I can't see the reason we should disallow Rust from making references to opaque types, it's just that actually using them, as in your example, is impossible without further knowledge (e.g. maybe it is documented that |
@madsmtm If the foreign type is the tail of the a struct, we want the field to be at a proper offset. Also, it's just very bad luck to say "It's OK to lie here because it doesn't matter", thing change including the whatever underpins that rational today. I prefer not to make decision decision that will cause massive headaches for designs (including future me) tomorrow. |
Yeah, it seems like for soundness we should disallow extern types to be used in non- |
I guess my understanding of extern types were more "opaque blob of bytes of unknown length" rather than "corresponds to a specific C type, we just don't know the size of the type". Under the former, I think it is perfectly rational that the extern type would include any padding, but of course it isn't under the latter. Note that I don't have any experience with language design, I'll believe you if you say the latter way of viewing extern types is correct! |
As someone who needs opaque pointers from time to time to interface with C, and who finds the workaround suggested in the Rustonomicon painfully ugly and error-prone, thank you for the summary, @Skepfyr. I wonder to what extent trying to solve the harder problem of also allowing opaque tails is blocking progress on stabilizing a much simpler first solution that would for now only enable opaque pointers, and which might hopefully already cover 95% of the actual uses. In particular, I personally would be delighted by a concept of an "thin opaque type" that would try to closely mirror the semantics of an incomplete struct in C and C++ as much as possible. In particular, it would
Such a solution would be perfectly open to future extensions that would enable more programs to compile if certain hints for the type are given.
I agree that post-monomorphization errors feel un-Rusty. Unfortunately, that seems almost unavoidable to be fully compatible with C here, which is what this feature is all about. |
I'm going to pretend that the following traits exist and that Sized isn't implied by default to make talking about all this easier: trait DynAligned { fn align(&self) -> usize; }
trait DynSized: DynAligned { fn size(&self) -> usize; } // This is what ?Sized means.
trait Aligned: DynAligned { const ALIGN: usize; }
trait Sized: Aligned + DynSized { const SIZE: usize; } // Equivalent to the existing Sized trait. Fundamentally some extern types need to be I'm not entirely convinced that what I said above is true, it's possible that all extern types have a alignment known at compile time, and We could avoid the above by not having traits and just making any issues be post-monomorphisation errors. There are some of those already but I cannot see that being acceptable, what's the point of traits if we just ignore them sometimes? On the other hand, the compiler does understand size and alignment unlike most traits, and we could make everything "just work" with post-monomorphisation errors (it would mean a bunch of special casing in the compiler though). I think that sorting all this out may require another RFC that supplants this one, given the kinds of questions that we're asking. |
I've opened rust-lang/rfcs#3396 in a bid to fix this properly. |
This is a tracking issue for RFC 1861 "Extern types".
Steps:
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?
In std's source, it is mentioned that LLVM expects
i8*
for C'svoid*
.We'd need to continue to hack this for the two
c_void
s in std and libc.But perhaps this should be done across-the-board for all extern types?
Somebody should check what Clang does. Also see "extern type" should use
opaque
type in LLVM #59095.The text was updated successfully, but these errors were encountered: