const
" #222fn { mod { (use) super::...; } }
and its interaction with derive patterns" #193Other pending proposals include:
Please update these in https://github.com/orgs/rust-lang/projects/31/views/7.
tmandry: We should schedule Rust for Linux if there will be a doc for it. Not clear whether that would happen yet or not.
Josh: Could also follow through with inviting someone from Bevy to talk about their list of issues new users encounter.
TC: The lang-team#235 has a doc ready from RalfJ.
tmandry: Making a short list, lang-team#235 should probably be discussed. Also RFL.
tmandry: Looking at the match ergonomics, some of those are edition-related, so we should prioritize.
tmandry: I'd propose we schedule that and the doc that RalfJ prepared. And we should tell the RFL folks that we definitely want to talk about that.
TC: Thoughts? Hearing none, sounds like consensus.
TC: Propose the match ergonomics first, as it's edition sensitive, then RalfJ's the week after.
Project board: https://github.com/orgs/rust-lang/projects/43
Link: https://github.com/rust-lang/rust/issues/114447
TC: I'm meaning to write an RFC about this.
tmandry: One question is do we need one.
scottmcm: I'd probably hope for an RFC here, but hopefully a short one. (It's something that people have been using in stable for a while, so it feels like "more" than just an issue, in a sense.)
Josh: We may want a &raw
syntax, so there could be churn here. Not a blocker, since we could suggest one thing, then later suggest another, just a consideration.
scottmcm: We really just want people to use SyncUnsafeCell
or to define their own type.
Josh: Should we reopen the discussion of forbidding static mut
?
scottmcm: Yes, but not for the 2024 edition.
scottmcm: I like RalfJ's suggestion here. We can discourage static mut
, but in the meantime to maybe later removing it, we can do this.
tmandry: Sounds like we want an RFC. I'll unnominate this.
Josh: Note that SyncUnsafeCell
only implements Sync
if T
implements Sync
. We may want an unsafe type that always implements Sync
.
scottmcm: That's surprising, since SyncUnsafeCell
was, I thought, designed for this use case.
(No issue link.)
Some background:
https://github.com/rust-lang/reference/pull/1042
Josh: So in the edition, we'd remove the behavior of removing multiple newlines.
tmandry: There's also something about only skipping increments of 4.
Josh: We should fix this.
waffle: This is probably complicated enough to merit an RFC.
waffle: Do we want a special syntax for multiline strings, like what Zig does?
tmandry: Anyone disagree this needs an RFC?
Josh: It needs clear documentation at least. I'd take it with a patch for the reference, or an RFC if others prefer that.
!
place is constructed (but not loaded!)" rust#117288Link: https://github.com/rust-lang/rust/issues/117288
(TC: We deferred this from triage to maybe discuss in the planning meeting.)
TC: @RalfJ found some interesting examples where rustc
generates SIGILL on code that shouldn't have UB. T-compiler marked this P-high. RalfJ:
Nominating this to get first vibes on which of the following lines should be accepted, and which should be UB. (Currently they are all accepted and all UB but that's a bug.)
#![feature(never_type)]
fn deref_never<T>() {
unsafe {
let x = 3u8;
let x: *const ! = &x as *const u8 as *const _;
let _val = addr_of!(*x); // must be OK
let _ = *x; // should probably be OK since we said `_` discards the place (and is UB-equivalent to `addr_of!`)
let _: ! = *x; // does a no-op type annotation force the place to be loaded and hence cause UB?
let _: () = *x; // is this UB? should we even accept such code?
let _: String = *x; // what about this?
let _: T = *x; // is this UB even if T = ! ?
}
}
TC: I asked RalfJ for a proposal for the right behavior on the ones with question marks. RalfJ:
I don't know how to arrive at a principled answer to this. There's no obvious "correct" here, it's a judgment call.
If you are asking for my personal preference ignoring backwards compatibility constraints, then my immediate gut feeling is that places of type
!
should not be subject to never-to-any coercions (the coercion should apply only to values, matching the comment that justifies the coercion in the first place). So:
let _: ! = *x; // OK, not UB
let _: () = *x; // does not compile
let _: String = *x; // does not compile
let _: T = *x; // does not compile
TC: What do we think?
Josh: Why do we allow?:
let x: *const ! = &x as *const u8 as *const _;
scottmcm: You can create any pointer and it's inhabited. The pointer is fine, but the read is invalid.
Josh: But that's not true at the type level. Any time you have a never is code that will never be run.
scottmcm: If you have a byte that is 3
, and you can have a *const bool
to it. So the pointer is fine, but it's a pointer you can't read without invoking UB. So that's entirely normal for raw pointers.
TC: +1 to scottmcm.
tmandry: +1 to scottmcm.
scottmcm: We can't even make a post-mono error, as it could be under an if false
. It's legal Rust code, just UB.
waffle: We shouldn't make these more special. It'd be worth making them less special.
scottmcm: Consider tuples and partial initialization.
Josh: If you have a *const (u32, !)
, can you dereference it if you only look at the u32
and never create a !
?
scottmcm: The question is, do we enforce validity rules on place creation or on place reading?
waffle: We really want to say that let _ = *x
does not make a read. We enforce validity when the place is converted to a value. If you're not using the place in any way, then you don't hit the invalidity.
scottmcm: Consider:
let p: *const (u32, !) = ...;
let q = ptr::addr_of!((*p).0); // uses only *place* operations, not values
waffle: Yes I think this should be correct.
Josh: So if we accept any of the proposals for pointer projection, then this would be fine.
scottmcm: So this would be justifiable…
This should be legal even if U=! and f1 panics.
pub fn blah<T, U>(f0: impl Fn() -> T, f1: impl Fn() -> U) -> (T, U) {
unsafe {
let mut mu: MaybeUninit<(T, U)> = MaybeUninit::uninit();
let p = mu.as_ptr_mut();
//(*addr_of_mut!((*p))).0 = f0();
// place projection here is fine, because it never became a value
(*p).0 = f0();
pointermagic.1 = f1();
return mu.assume_init();
}
}
waffle: An invalid type is OK in a place, but it's UB when you coerce that into a value.
scottmcm: The code above is the same as the original argument that these tuples can't be ZSTs because of what compiler does internally. The compiler already wants to write code like this (at least to LLVM) for tuple creation.
waffle:
// Ralf's suggestion from above
// x: *const !
// OK because we say that `_` does not covert a
// place to a value.
let _: ! = *x; // OK, not UB
// This should not compile because if it compiled,
// it would make it so that we converted never to unit,
// and that would be immediate UB.
let _: () = *x; // does not compile
// because this is actually
let _: () = (*x) as ();
// and thus there's a value,
// and thus we don't *want* it to compile because that'd be super confusing
// Q: is this also doing a coercion
// in a world where it hits type fallback instead
// of inferring to `!`? (Since nothing infers to `!` today)
// like if someone uses the never-type-on-stable hack...
//
// A: yes
let _ = *x; //~ Shouldn't compile because it infers unit and be UB, today. But we want it to not compile.
// Today: compiles as UB because it infers as `()` due to fallback
// Wish: doesn't compile because oh god no.
let _: String = *x; // does not compile
let _: T = *x; // does not compile
enum Never {}
let y: *const Never = 1 as _; //~ OK today.
// this doesn't compile today,
// because there is no Never -> () place coercion which is probably incorrect with !
let _: () = *y; //~ ERROR: type mismatch
// This still will be UB:
let _: () = {*x};
Josh: No objection any longer about what was mentioned above. Happy to defer to expert analysis here.
tmandry: Stabilizing !
is one of the 2024 issues up for discussion.
TC: If there are things we could do in 2024 that would help make it possible to evetually do the never type, we should look at that.
scottmcm: If we stopped doing this coercion, and people had to write it out, that's one thing we could do.
scottmcm: second attempt at stabilizing never: https://github.com/rust-lang/rust/issues/57012
scottmcm: Nothing ever infers to !
today because it's not a type on stable. It's a weird magic syntax. So reading a pointer to !
, if you do a hack to get it, is that the value you get back will infer to unit, because that's what type parameter fallback does in these cases, which is why we're having trouble stabilizing never in the first place. So because of that type parameter fallback, you get coercions in places that wouldn't be corecions for literally any other type.
waffle: That's correct.
waffle: Simple explanation:
// We want to say that this:
let _ = *x; //~ Does not do a read.
This implies that places don't enforce validity invariants.
Which means that a place coercion of ! -> T
is invalid, because it assumes that !
's validity invariant (false) was enforced.
I.e. The place coercion of ! -> T
(any type T
) is a compiler bug. It's wrong that we accept this code today.
If you have a value of never, then you can coerce it. But you can't validly do that to a place.
TC: Does this have bearing on the never stabilization?
scottmcm: We can't remove the value coercion since we need that. But if we could figure out how to remove the value coercion, that would make it more possible.
tmandry: We could change how the coercions work with the edition. We could make it go from being implicit to explicit.
scottmcm: Of course, if it's explicit, it's not a coercion.
tmandry: It's a cast.
scottmcm: There's no place cast in stable Rust. In this case, we'll get rid of the place coercion, so we don't need a place cast.
tmandry: Sounds good to me. I trust you all to figure this out.
TC: Could we make those value coercions into casts, over an edition?
scottmcm: Maybe? It's hard to make confident statements in this area.
scottmcm: If we could notice what you're calling is a function that returns !
and treat that as control-flow rather than value flow somehow, and do that in a way that is edition dependent. But this is all slightly wrong because of how we do HIR building.
scottmcm: Theoretically the only thing you can do with !
today is to have a value return from a function. It's technically wrong to use it elsewhere. It's officially unstable, so maybe we could carve something out.
TC: We've discussed how stabilizing Infallible
made more difficult the stabilization of the never type.
scottmcm: It's because we want Infallible
to be a type alias to never.
TC: Is there any sense in that all emtpy enums should coerce to !
?
scottmcm: Maybe. This may get into the problem of type parameter fallback.
let x = returns_bang(); // x is of type `()`
let y = returns_infallible(); // y is of type `Infallible`
waffle: If I recall correctly, the problem with type fallback is that for generic functions, we currently infer unit, but we want to infer bang. But if we infer bang, then existing code become UB.
scottmcm:
if false { panic!() } else { transmute(()) }
This is not UB today. But if the panic!()
inferred to bang, then it would be UB.
A trunsmute creating a !
is obvious immediate UB. Since the transmute is unconstrained, changing the type of panic!()
changes the dst
type of transmute
from unit to bang.
TC: We needed to circle back to:
let _: () = *x; // Insta-UB or compiler error?
scottmcm: Even if we say that the place can't be coerced, that could still do the place to value coercion, and then convert that.
waffle: The place to value coercion happens at the let
level…
scottmcm: And we wouldn't be asking for the value?
TC: It seems like we're saying it's at least insta-UB, and any insta-UB that the compiler can find is of course good.
scottmcm: I might put this in a deny-by-default lint rather than a compiler error category.
TC: +1.
scottmcm: You only get the coercion if it's literally a coercion to !
. If it's a pointer to a generic type, and that happens to be substituted to !
, then…
waffle:
unsafe fn foo<T>(p: *const T) {
// doesn't compile because generics don't work like that
let _: () = *p; //~ ERROR
}
// so you only hit that if it's literally bang, like
unsafe fn bar(p: *const !) {
let _: () = *p;
}
// and nobody would ever write that.
scottmcm: If bar
is UB to call but compiles, then I'm OK with that. Don't write that.
scottmcm: Do we want a crater run?
TC: What would we expect crater to hit?
waffle: Not doing place coercions should make strictly less code compile.
scottmcm: What classes of things would hit this?
scottmcm: If we could make this be the rule, that'd be great.
Consensus: It's not UB to have an invalid thing in a place. But it's UB to coerce that invalid place to a value. And anywhere that the compiler can statically determine that insta-UB is happening, that would be a good spot for a deny-by-default lint, and it'd also be fine to hard error on those cases if that's easier.
Ralf's comment here https://github.com/rust-lang/rust/issues/117288#issuecomment-1813030483
scottmcm: Where this may break is the use of the never type hack, but that's probably OK.
waffle:
trait Hack {
type Out;
}
impl<T> Hack for fn() -> T {
type Out = T;
}
type Never = <fn() -> ! as Hack>::Out;
scottmcm: More details: https://github.com/rust-lang/never-type-initiative
(The meeting ended here.)
Link: https://github.com/rust-lang/lang-team/issues/21
Link: https://github.com/rust-lang/lang-team/issues/22
Link: https://github.com/rust-lang/lang-team/issues/51
Link: https://github.com/rust-lang/lang-team/issues/88
Link: https://github.com/rust-lang/lang-team/issues/137
Link: https://github.com/rust-lang/lang-team/issues/149
None.
Link: https://github.com/rust-lang/lang-team/issues/21
Link: https://github.com/rust-lang/lang-team/issues/22
Link: https://github.com/rust-lang/lang-team/issues/48
Link: https://github.com/rust-lang/lang-team/issues/51
Link: https://github.com/rust-lang/lang-team/issues/54
Link: https://github.com/rust-lang/lang-team/issues/80
Link: https://github.com/rust-lang/lang-team/issues/87
Link: https://github.com/rust-lang/lang-team/issues/88
Link: https://github.com/rust-lang/lang-team/issues/123
Link: https://github.com/rust-lang/lang-team/issues/137
Link: https://github.com/rust-lang/lang-team/issues/149
Link: https://github.com/rust-lang/lang-team/issues/172
Link: https://github.com/rust-lang/lang-team/issues/185
Link: https://github.com/rust-lang/lang-team/issues/186
Link: https://github.com/rust-lang/lang-team/issues/189
fn { mod { (use) super::...; } }
and its interaction with derive patterns" lang-team#193Link: https://github.com/rust-lang/lang-team/issues/193
Link: https://github.com/rust-lang/lang-team/issues/199
Link: https://github.com/rust-lang/lang-team/issues/204
Link: https://github.com/rust-lang/lang-team/issues/209
Link: https://github.com/rust-lang/lang-team/issues/211
impl Trait
" lang-team#215Link: https://github.com/rust-lang/lang-team/issues/215
Link: https://github.com/rust-lang/lang-team/issues/217
Link: https://github.com/rust-lang/lang-team/issues/218
Link: https://github.com/rust-lang/lang-team/issues/219
Link: https://github.com/rust-lang/lang-team/issues/220
Link: https://github.com/rust-lang/lang-team/issues/221
const
" lang-team#222Link: https://github.com/rust-lang/lang-team/issues/222
Link: https://github.com/rust-lang/lang-team/issues/223
Link: https://github.com/rust-lang/lang-team/issues/224
Link: https://github.com/rust-lang/lang-team/issues/229
Link: https://github.com/rust-lang/lang-team/issues/232
Link: https://github.com/rust-lang/lang-team/issues/235
Link: https://github.com/rust-lang/lang-team/pull/236
Link: https://github.com/rust-lang/lang-team/pull/237