-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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 type
cannot support size_of_val
and align_of_val
#49708
Comments
@rfcbot fcp merge |
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged teams: No concerns currently listed. Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
will the lint be emitted before or after monomorphization? P.S. cc rust-lang/rfcs#2310 |
I'm specifically in favor of "before", only emitting anything after monomorphization under some general umbrella of "this code will always panic at runtime" warnings. |
It would be nice to handle generic functions that get monomorphized with an |
The RFC text (and the discussion even more so, IIRC) mention the possibility of adding a new trait for encoding this constraint. I assume this has been dropped because that trait would have to be included in bounds by default like |
@rkruppe yes sadly.... rust-lang/rfcs#2310 was opened as backup plan. See the end of #43467. |
I'm a bit confused why this is in rust-lang/rust instead of rust-lang/rfcs. (I have rust-lang/rfcs set as watched, so I get email copies of all issues and PRs there, but not here.) |
Because it's a bug about |
Because this is not a new RFC, it's nailing down a specific unresolved question around extern types (though we should have linked from the tracking issue to here -- fixed). This is pretty standard practice. |
Why lint when you can have type safety? Two convincing comments from the other discussion thread:
And my own perspective: there are various places where I'd like to be generic over things that the language doesn't allow. That goes hand in hand with the type system. For example, you can't be generic over lengths when using arrays or calling conventions when using function pointers. Let's not add more features in that could reasonably be described correctly in the type system but aren't. |
If the lint is deny-by-default (which I think it should be), it'd effectively act the same as a type error. The second comment you quote seems based on the idea that it'd be a warning and not an error. So the only difference is whether to provide a more general trait-based mechanism or to handle the specific case at hand. I haven't seen any fundamental opposition to the idea of introducing those traits in the future if we have a more general case that needs them, but if we want those traits for some future features that we haven't yet introduced to the language, let's design those traits alongside those future language features. |
Would it be possible to make substituting an // ok:
fn f1(a: &ExternType) {}
fn f2() -> *mut ExternType {}
fn f3<T: ?Sized>(a: &T, b: *const ExternType) {}
fn f4<A, R, F: Fn(A) -> R>(func: F, a: A) -> R {}
let w1 = size_of::<*const ExternType>();
let w2 = size_of_val(&f2());
let w3 = f4(|x| x, &*f2());
//^ substituting `&ExternType` is ok.
// errors (gated):
fn g1<T: ?Sized>(a: &T) {}
g1(&*f2()); //~ ERROR: Cannot substitute an extern type to `T`
fn g2() -> Box<ExternType> {} //~ ERROR: Cannot substitute an extern type to `T`
struct S1(ExternType); //~ ERROR: Cannot use an extern type as struct field.
struct S2<R: ?Sized>(R);
let s2: S2<ExternType>; //~ ERROR: Cannot substitute an extern type to `R`
struct S3;
impl Deref for S3 {
type Target = ExternType; //~ ERROR: Cannot use extern type as associated type.
fn deref(&self) -> &ExternType {}
} |
In terms of trust, only lints that cannot be turned off allow foreign code to be trusted to the same degree. The generics thing that @jethrogb points out is good too. Let's assume we have a monomorphization-time lint as you proposed, @joshtriplett (I agree with you that that's better than only having a pre-monomorphization time lint). Then, even thought its just as safe (same things will be prevented in the end), the user experience is still worse. The library could be fine over it's testsuite, but not fine when instantiated downstream. And for upstream, even if the test do catch such bad instantiations, type errors pop up sooner than monomorization-time lints for a quicker debug cycle. |
@kennytm I mentioned before in a buried comment that we can stabilize In many ways, that is effectively the same as what you propose for stable code, unstable |
@Ericson2314 That's a much bigger change because you need to introduce a new concept Furthermore we are not even committed to whether |
@kennytm
Nothing I propose forces a final decision, since
|
How does this relate to #48055? It seems like that is another case where we'd assume for arbitrary |
Well, not necessarily out of the box, without a way to get an owned Anyway, definitely seems like a good idea to restrict that feature to |
I propose that it be (1) deny-by-default, (2) not allowed to be changed from that default, and (3) formatted to look like a type error instead of a lint message. Then it would be indistinguishable, to the user, from a type error. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
I still think that when we have the option of enforcing a typing rule in the type checker vs. the lint mechanism, we should enforce it in the type checker. I understand and don't necessarily disagree with the objections to |
There is precedent for such no-so-elegant special-casing: instanciating |
The final comment period, with a disposition to merge, as per the review above, is now complete. |
I'd like to bring up the idea of only allowing extern types behind raw pointers, and not behind references or by value again (no matter whether that's implemented via a permanently unstable bound or whatever special behavior). I've proposed allowing getting the layout of a raw pointer's pointee type, and while The main issue with "extern type only behind pointer" (ignoring implementation concerns) is that the RFC explicitly mentions using extern types for FAM-like scenarios: extern {
type OpaqueTail;
}
#[repr(C)]
struct FfiStruct {
data: u8,
more_data: u32,
tail: OpaqueTail,
} While suboptimal, the same effect can be achieved by just making extern {
type FfiStruct;
type OpaqueTail;
}
impl FfiStruct {
fn data(*mut self) -> *mut u8 { // though it's a lot less convenient without pointer receivers
self.cast::<u8>().offset(0).cast::<u8>()
}
fn more_data(*mut self) -> *mut u32 {
self.cast::<u8>().offset(4).cast::<u32>()
}
} Alternatively, you could still allow extern type as an unsized tail of a struct, but make that "poison" the struct with whatever poison makes extern type unusable behind a reference or by-value. It seems to me that this is the most "theoretically pure" solution to the " |
I'm not really sure how we could do that without some kind of trait bound, unless the intent is to not permit generics to be bound to extern types. In particular, you could have |
Well, not that exact formulation, as What is allowed is But yes, while I now agree that while this (no reference to |
I know this has been brought up in random places but hasn't been discussed here yet. We could require methods on extern types for size and alignment. That way we leave the actual decision to users:
|
That is better than just "panics + usable in generics", but it feels like an ad-hoc bootleg version of |
It doesn't require adding a new opt-out trait and adding bounds for it to various functions. |
But it does require adding a new ad-hoc monomorphization passes and limmitting the instantiantiation of generics. That strikes me as exactly the same amount of work in rustc as using the existing proven mechanism (opt-out traits).
I don't understand this? What's the actual burden here?
So you are saying that syntax is unstable, but the existence of extern types and instantiating them in your own generics is stable? Isn't that just what we have today? Conversely, we could implement It really seems like the ideological opposition to opt-in traits is leading everyone down weird paths! I am not sure what to say than these alternatives seems like massive epicycles and code smells to me, and I am kinda shocked few other folks seem to have the same gut reaction --- didn't we all agree panicks are post-monomorphization lints and their ilk are bad? |
I'm trying to say that we should stabilize only extern {
type Foo {
fn size(self: *const Self) -> usize {
strlen(self as *const u8)
}
}
} and figure out if we want the "rejected in generics" form of extern types later. After that change people can use the panicking variant, which isn't too great, but not too bad either. We could even figure out a separate opt out trait for rejecting types to be used in generics by default that is independent of |
that seems like a reasonable implementation of
If we allow people to write their own So yea, either an opt out trait that users never see or compiler magic to reject them seems fine to me now. The opt out trait seems nicer than other compiler magic, tho due to it needing to be a lang-item I'm not sure it's actually cleaner, we can try both impls. |
(FWIW the opposition to new opt-out traits is certainly not ideological, it is based on concrete concerns. We all want to make Rust the best language it can be, we just disagree on how to best achieve that. So let's stay constructive. :) |
I am still trying to understand this. Are you saying with the generics prohibition, then |
Also, keep in mind we a very analogous situations to the status quo were if an empty type is the last field, then the outer type must also be prohibited from instantiations. This and probably other DST logic I am forgetting tip the scales for me. I would like to reuse all that code! |
OK well to lay my cards out on the table, I think this would be a good state of affairs:
So I could see (2) being viewed as "backstabbing" the perma unstable decision; I'm certainly willing to admit that. On the other hand, going with ad-hoc implementation strategies rather than traits feels like:
I am not trying to stir the pot and allege you all are consciously thinking that, but that's just what it has felt like being on the other end. The pitch to you all with the compromise path I outlined is basically:
Allowing I would never normally say "please change |
I like experimenting with |
Woo cannot heart that enough!
Oh I didn't know (or long ago forgot) about that. Thrilled to hear this is such precedent for "Compiler-internal traits" too! |
It's not obvious to me how much a forever-unstable |
@scottmcm Well we are saying if people indeed want to do that, then there will be preassure to stabilize that. And perhaps that's not a bad thing, it coming from real users solving imminent problems, rather than speculation from me and others about how problems in the abstract ought to be addressed. Still, it's good to think: what are the alternatives?
Given those alternatives I think starting with |
I'm not opposed to have In other words, one bit of feedback I would be interested in is--- how often did you find compilation errors related to this trait helping to clarify your thinking ("ah, of course I can't do it that way, we might not know the size!") etc. |
@nikomatsakis Glad to hear that!! My heterodox hunch is that actually most
I am thinking that if
:) This is all very roundabout way of saying if today's situation is indeed a bit of an "anti-huffman encoding" of people's desires as I allege, bug empiricism might be sort of a statistically wonky, and even ignoring |
How about just make usage of |
@nbdd0121Post monomorphization errors are generally regarded a terrible developer experience akin to C++ template errors. @nikomatsakis saying that we can try out |
Based on discussion on #43467 and in @rust-lang/lang meetings, an opaque
extern type
type cannot supportsize_of_val
oralign_of_val
. We believe that we should panic or abort in this case. We also believe that we should have a compile-time lint that detects this whenever possible, since at some level the compiler should know at compile time if code invokessize_of_val
oralign_of_val
on anextern type
.I've opened this separate issue to document that decision, and will propose FCP on it to give time for final comments.
The text was updated successfully, but these errors were encountered: