-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Path Inference #3444
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
base: master
Are you sure you want to change the base?
Path Inference #3444
Conversation
|
I'm not necessarily against the RFC, but the motivation and the RFC's change seem completely separate. I don't understand how "people have to import too many things to make serious projects" leads to "and now |
In crates like use windows::{
core::*, Data::Xml::Dom::*, Win32::Foundation::*, Win32::System::Threading::*,
Win32::UI::WindowsAndMessaging::*,
}; |
|
Even assuming I agreed that's bad practice (which, I don't), it is not clear how that motivation has lead to this proposed change. |
How can I make this RFC more convincing? I am really new to this and seeing as you are a contributor I would like to ask for your help. |
|
First, I'm not actually on any team officially, so please don't take my comments with too much weight. That said:
Here's my question: Is your thinking that an expansion of inference will let people import less types, and then that would cause them to use glob imports less? Assuming yes, well this inference change wouldn't make me glob import less. I like the glob imports. I want to write it once and just "make the compiler stop bugging me" about something that frankly always feels unimportant. I know it's obviously not actually unimportant but it feels unimportant to stop and tell the compiler silly details over and over. Even if the user doesn't have to import as many types they still have to import all the functions, so if we're assuming that "too many imports" is the problem and that reducing the number below some unknown threshold will make people not use glob imports, I'm not sure this change reduces the number of imports below that magic threshold. Because for me the threshold can be as low as two items. If I'm adding a second item from the same module and I think I might ever want a third from the same place I'll just make it a glob. Is the problem with glob imports that they're not explicit enough about where things come from? Because if the type of I hope this isn't too harsh all at once, and I think more inference might be good, but I'm just not clear what your line of reasoning is about how the problem leads to this specific solution. |
Part of it yes, but, I sometimes get really frustrated that I keep having to specify types and that simple things like match statements require me to sepcigy the type every single time.
Its imported in the background. Although we don't need the exact path, the compiler knows and it can be listed in the rust doc.
Definitely not, you point out some great points and your constructive feedback is welcome. |
|
Personally |
|
I would like to suggest an alternative rigorous definition that satisfies the examples mentioned in the RFC (although not very intuitive imo): When one of the following expression forms (set A) is encountered as the top-level expression in the following positions (set B), the Set A:
Set B:
Set B only applies when the type of the expression at the position can be inferred without resolving the expression itself. Note that this definition explicitly states that Set B does not involve macros. Whether this works for macros like Set A is a pretty arbitrary list for things that typically seem to want the expected type. We aren't really inferring anything in set A, just blind expansion based on the inference from set B. These lists will need to be constantly maintained and updated when new expression types/positions appear. |
That is so useful! Let me fix it now. |
|
One interesting quirk to think about (although unlikely): fn foo<T: Default>(t: T) {}
foo(_::default())should this be allowed? we are not dealing with type inference here, but more like "trait inference". |
I think you would have to specify the type arg on this one because fn foo<T: Default>(t: T) {}
foo::<StructImplementingDefault>(_::default()) |
|
oh never mind, right, we don't really need to reference the trait directly either way. |
|
I've been putting off reading this RFC, and looking at the latest version, I can definitely feel like once the aesthetic arguments are put aside, the motivation isn't really there. And honestly, it's a bit weird to me to realise how relatively okay I am with glob imports in Rust, considering how I often despise them in other languages like JavaScript. The main reason for this is that basically all of the tools in the Rust ecosystem directly interface with compiler internals one way or another, even if by reimplementing parts of the compiler in the case of In the JS ecosystem, if you see a glob import, all hope is essentially lost. You can try and strip away all of the unreasonable ways of interfacing with names like eval but ultimately, unless you want to reimplement the module system yourself and do a lot of work, a person seeing a glob import knows as much as a machine reading it does. This isn't the case for Rust, and something like So really, this is an aesthetic argument. And honestly… I don't think that importing everything by glob, or by name, is really that big a deal, especially with adequate tooling. Even renaming things. Ultimately, I'm not super against this feature in principle. But I'm also not really sure if it's worth it. Rust's type inference is robust and I don't think it would run into technical issues, just… I don't really know if it's worth the effort. |
|
@clarfonthey glob imports easily have name collision when using multiple globs in the same module. And it is really common with names like |
I can understand your point, but, when using large libraries in conjunction, like @SOF3 said, it can be easy to run into name collisions. I use actix and seaorm and they often have simular type names. |
|
Right, I should probably clarify my position-- I think that not liking globs is valid, but I also think that using globs is more viable in Rust than in other languages. Meaning, it's both easier to use globs successfully, and also easier to just import everything you need successfully. Rebinding is a bit harder, but still doable. Since seeing how useful Even if you're specifically scoping various types to modules since they conflict, that's still just the first letter of the module, autocomplete, two colons, the first letter of the type, autocomplete. Which may be more to type than My main opinion here is that Like, I'm not convinced that this can't be better solved by improving APIs. Like, for example, you mentioned that types commonly in preludes for different crates used together often share names. I think that this is bad API design, personally, but maybe I'm just not getting it. |
|
I do think inferred types are useful when matching for brevity's sake: #[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct Reg(pub Option<NonZeroU8>);
#[derive(Debug)]
pub struct Regs {
pub pc: u32,
pub regs: [u32; 31],
}
impl Regs {
pub fn reg(&self, reg: Reg) -> u32 {
reg.0.map_or(0, |reg| self.regs[reg.get() - 1])
}
pub fn set_reg(&mut self, reg: Reg, value: u32) {
if let Some(reg) = reg {
self.regs[reg.get() - 1] = value;
}
}
}
#[derive(Debug)]
pub struct Memory {
bytes: Box<[u8]>,
}
impl Memory {
pub fn read_bytes<const N: usize>(&self, mut addr: u32) -> [u8; N] {
let mut retval = [0u8; N];
for v in &mut retval {
*v = self.bytes[addr.try_into().unwrap()];
addr = addr.wrapping_add(1);
}
retval
}
pub fn write_bytes<const N: usize>(&mut self, mut addr: u32, bytes: [u8; N]) {
for v in bytes {
self.bytes[addr.try_into().unwrap()] = v;
addr = addr.wrapping_add(1);
}
}
}
pub fn run_one_insn(regs: &mut Regs, mem: &mut Memory) {
let insn = Insn::decode(u32::from_le_bytes(mem.read_bytes(regs.pc))).unwrap();
match insn {
_::RType(_ { rd, rs1, rs2, rest: _::Add }) => {
regs.set_reg(rd, regs.reg(rs1).wrapping_add(regs.reg(rs2)));
}
_::RType(_ { rd, rs1, rs2, rest: _::Sub }) => {
regs.set_reg(rd, regs.reg(rs1).wrapping_sub(regs.reg(rs2)));
}
_::RType(_ { rd, rs1, rs2, rest: _::Sll }) => {
regs.set_reg(rd, regs.reg(rs1).wrapping_shl(regs.reg(rs2)));
}
_::RType(_ { rd, rs1, rs2, rest: _::Slt }) => {
regs.set_reg(rd, ((regs.reg(rs1) as i32) < regs.reg(rs2) as i32) as u32);
}
_::RType(_ { rd, rs1, rs2, rest: _::Sltu }) => {
regs.set_reg(rd, (regs.reg(rs1) < regs.reg(rs2)) as u32);
}
// ...
_::IType(_ { rd, rs1, imm, rest: _::Jalr }) => {
let pc = regs.reg(rs1).wrapping_add(imm as u32) & !1;
regs.set_reg(rd, regs.pc.wrapping_add(4));
regs.pc = pc;
return;
}
_::IType(_ { rd, rs1, imm, rest: _::Lb }) => {
let [v] = mem.read_bytes(regs.reg(rs1).wrapping_add(imm as u32));
regs.set_reg(rd, v as i8 as u32);
}
_::IType(_ { rd, rs1, imm, rest: _::Lh }) => {
let v = mem.read_bytes(regs.reg(rs1).wrapping_add(imm as u32));
regs.set_reg(rd, i16::from_le_bytes(v) as u32);
}
_::IType(_ { rd, rs1, imm, rest: _::Lw }) => {
let v = mem.read_bytes(regs.reg(rs1).wrapping_add(imm as u32));
regs.set_reg(rd, u32::from_le_bytes(v));
}
// ...
}
regs.pc = regs.pc.wrapping_add(4);
}
pub enum Insn {
RType(RTypeInsn),
IType(ITypeInsn),
SType(STypeInsn),
BType(BTypeInsn),
UType(UTypeInsn),
JType(JTypeInsn),
}
impl Insn {
pub fn decode(v: u32) -> Option<Self> {
// ...
}
}
pub struct RTypeInsn {
pub rd: Reg,
pub rs1: Reg,
pub rs2: Reg,
pub rest: RTypeInsnRest,
}
pub enum RTypeInsnRest {
Add,
Sub,
Sll,
Slt,
Sltu,
Xor,
Srl,
Sra,
Or,
And,
}
pub struct ITypeInsn {
pub rd: Reg,
pub rs1: Reg,
pub imm: i16,
pub rest: ITypeInsnRest,
}
pub enum ITypeInsnRest {
Jalr,
Lb,
Lh,
Lw,
Lbu,
Lhu,
Addi,
Slti,
Sltiu,
Xori,
Ori,
Andi,
Slli,
Srli,
Srai,
Fence,
FenceTso,
Pause,
Ecall,
Ebreak,
}
// rest of enums ... |
|
I do like type inference for struct literals and enum variants. However, type inference for associated functions doesn't make sense to me. Given this example: fn expect_foo(_: Foo) {}
foo(_::bar());
All in all, it feels like this would add a lot of complexity and make the language less consistent and harder to learn. Footnotes
|
|
Regarding structs and enums: The RFC didn't explicitly mention this, but I think that tuple structs, tuple enum variants, and unit structs should also be inferrable: enum MyEnum {
NormalVariant { a: i32 },
TupleVariant(i32),
UnitVariant,
}
struct NormalStruct { a: i32 }
struct TupleStruct(i32);
struct UnitStruct;
fn expect_enum(_: MyEnum) {}
fn expect_normal_struct(_: NormalStruct) {}
fn expect_tuple_struct(_: TupleStruct) {}
fn expect_unit_struct(_: UnitStruct) {}
expect_enum(_::NormalVariant { a: 42 });
expect_enum(_::TupleVariant(42));
expect_enum(_::UnitVariant);
expect_normal_struct(_ { a: 42 });
expect_tuple_struct(_(42));
expect_unit_struct(_); |
|
I use a trait which contains a function that takes an argument of a type which is only accessed as an associated type, and being able to replace all of that with a |
Just gonna break down my thought here when I read this:
I'm not sure what's gained by doing this instead of adding a single |
That actually simplifies a lot! I brought up the It clears up the biggest mess of syntax, and leaves open the value spread/splat RFC. And its just not a pattern anyone should ever use. It should be fine in updated fields, as an example, but just not the trailing part, where it expects an existing
Having used this shorthand with a What is probably my most preferred sigil is actually After |
|
What do you mean by "shape"? Structural typing? Because this should be type inference, not structural typing - the shape of type should not affect the inference. |
|
Ah, yeah, it wasn't meant to be taken exactly. I tried (and failed 😆 ) conveying a hand wavy nature, trying to hint at the compiler magically making it work. I know type inference will be what is used here to match the type, but just wanted to try to convey a sort of dumb concept around matching syntax to a "do the thing" thing, from its own perspective. A very dumb thing on my part, sorry for the confusion. |
|
In swift et al, "." is the namespace separator, so .Variant is just leaving off the name of the enum, but in Rust that would be I don't think using "." is terrible. I could certainly live with it. But it also feels a little out of place. Would ":" be workable? |
i think you need to demonstrate how this won't have the same negative effect to diagnostic that leads to #3307. (actually the dot syntax may have the same issue, e.g. you'll accidentally invoke the feature in let foo = Foo::builder(); // <- accidental `;`
.config_1() // <- 🤔
.build();) |
|
For destructing in a function (don't believe this is part of the actual proposal, but trying to be forward looking), it seems a little weird with the fn foo(:( bar, baz ): BarBaz) {}
fn foo(:{ bar, baz }: BarBaz) {}As for the unintentional invocation for .config_1()This would only be for a variant syntax, which would need something inside the |
Tuple variants can be empty in Rust: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=479522a0a68d14851244a2e0839d1e47 |
I don't think it would be quite as bad as type ascription, since, for example, missing a colon in the :: operator wouldn't be a valid place for path inference, since it can't come immediately after an identifier. But I'm not familiar enough with the diagnostic system to know how much (or how little) of a problem it would be. |
Had never seen that before! Interesting. I wonder what you can do with this over just |
|
I would like to bring up the idea of Prior statements:
The only ambiguity mentioned here is a future-compatibility concern against what … And if IMO it’s quite the opposite of ambiguous (or confusing): Fully qualified paths are almost always longer than 1 segment anyways. When is the last time you would have used something silly such as the following? Besides this niche case in Here’s some examples I had written, imagining in more variety of what simply using enum Demo {
Variant1,
Variant2,
OtherVar,
SomeTup(Foo, Bar),
AndStructStyle {
field1: i32,
extra: Baz,
moar_field: Qux,
},
}
fn consume(d: Demo) {
match d {
::Variant1 => {
// code
}
::Variant2, ::OtherVar => {
// code 2
}
::SomeTup(foo, _) => {
// some code
}
::AndStructStyle {
field1: 123,
extra,
..
} => {
// handling
// code
}
catch_all => {
// keep calm and don't panic
}
}
}
fn consume_inline(d: Demo) {
match d {
::Variant1 => /* code */,
::Variant2, ::OtherVar => /* code 2 */,
::SomeTup(foo, _) => /* some code */,
::AndStructStyle {
field1: 123,
extra,
..
} => {
// handling
// code
}
catch_all => /* keep calm and don't panic */,
}
}I think this RFC is most useful for enums, and could be quite beneficial even if initially only implemented / stabilized for enums alone. But in principle, struct Cool {
field: Ty,
other_field: Ty2,
}
struct LikeATuple(A, B, Cool);
fn need_tup_struct(t: LikeATuple) {
let _field = t.2.field;
}
fn usage1() {
// let's call
let field = f();
let c = :: {
other_field: something(),
field,
};
// conventionally unsure about `:: {` vs `::{`
// the former would be more consistent with the usual spacing
// like `Cool {`.
// And we have tuple structs…
let _ = need_tup_struct(::(foo1(), bar(), c));
// Which can get ugly, I guess
let _ = need_tup_struct(::(::(foo()), bar(), c));
// but maybe let's just format these cases?
let _ = need_tup_struct(
::(
::(foo()),
bar(),
c,
),
);
}// some different example I found in the forum thread I’m just coming from
// [ https://users.rust-lang.org/t/path-inference-syntax-variant/136930/30 ]
// adapted to this syntax
fn test() {
set_wireless_config(:: {
wlan: ::AccessPoint,
bluetooth: ::Enabled,
});
}though this does involve It could even be fully stand-alone for unit structs, I guess? // Unit structs?
struct Unity;
fn need_unity(_: Unity) {
// wow
}
fn usage2() {
// let's call it
need_unity(::);
// is this ^^ too cursed??
}
fn wow() {
// if you are more used to “naked” `::`, then spacing
// like this vvvv doesn’t look so unnatural anymore.
let _value = :: {
/* … */
};
// but IMHO this isn’t so unattractive either 🤷♂
let _value = ::{
/* … */
};
}The exact technical solution for distinguishing Footnotes
|
|
|
||
| Status::Pending<f64, Foo>(0.0); | ||
| Status::Complete<f64, Foo> { data: Foo::default() }; | ||
| ``` |
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 isn’t the correct current syntax. It would need to be
Status::Pending::<f64, Foo>(0.0);
Status::Complete::<f64, Foo> { data: Foo::default() };with additional :: tokens after Pending/Complete, before the < tokens.
The section that follows then doesn’t correctly build analogy, either, of course. Maybe the “analogous” form then would be something like the following?
.Pending::<f64, Foo>(0.0);
.Complete::<f64, Foo> { data: Foo::default() };For better understanding / completeness, in the former section, it might also be worth mentioning somehow that Status::Pending::<f64, Foo>(0.0) can also be written as Status::<f64, Foo>::Pending(0.0) in current Rust. So the former is just sugar; though for the case of .Pending, there wouldn’t really be any way to put the parameters before the variant name1, (which is a similar argument as for already the case for enum constructors that are use’d which – the need for Pending::<f64, Foo>(0.0) with use Status::Pending; – presumably motivated the existence of Status::Pending::<f64, Foo>(0.0) syntax in the first place.)
Footnotes
-
Nevermind, maybe it’s not actually impossible. I just read the rest of the RFC and noticed that that’s even a mentioned possible / considered alternative [“
.<u8, String>::Ok(42)”], and I see no immediate reason why this would be impossible. ↩
|
So even if |
|
one issue with
|
How is that an argument for why it’s worse though? Wouldn’t it already being legal mean less possibility for problems, because it’s already proven syntax? This should help not only with avoiding technical challenges, but also human ones; after all, an existing Rust user will have a much easier time understanding that From that angle, I don’t really see the “beauty” in Also, these languages that do use
Of course don’t agree with the last sentence,
I don’t understand the future compatibiltiy concern. Is this just abstract raising of fear/uncertainty/doubts or are there actual concerns? My whole prior reply was based on my dissatisfaction with the ambigity-related counter-arguments, and I even explicitly noted in a footnote that “I’m looking forward to any replies that would point”. IMO, the argument isn’t convoluted, it’s strong. It’s only lengthy because there are so many separate, independend ways in which (especially for human readers in pracrical programs) it’s an absolute non-issue. First: Each one of these on its own could suffice to avoid all practical issues. Conventionally, crate names are lower-case, variant names upper-case. (We already use the same distinction - for practical purposes - to differentiate between variants and variables in patterns;1 and in fact, this very RFC may drastically improve the situation there as people can just write Second: The length difference… Third: I don’t know why I haven’t mentioned this yet – nobody even uses (It’s probably a reasonable thing to look into as to why exactly that was changed though and if it constitutes any new counter-arguments… I haven’t done this in-depth yet.)
I think this is a fair point. I would like to note that I personally feel – about this point – that the problem for structs is a lot less pressing anyway, and this RFC may potentially benefit from downsizing to fully nailing down the case of Having given it a second thought now, I do feel like using struct Unit;
fn usage() {
let x: Unit = _;
match x {
_ => { /* nothing new here! */ }
}
}whereas I hadn’t really considered the arguable weirdness of struct Unit;
fn usage() {
let x: Unit = ::; // <- not really satisfying
match x {
:: => { /* I don’t really like it myself :-/ */ }
}
}
As an added bonus, with Regarding the argument of “ That’s particularly true for constructors of structs/enums, where you can rewrite, for instance, [On the other hand, another issue with the In any case, I belive adding Footnotes
|
That’s a good valid point to bring up, thank you! Here would be around 7 possibly counter arguments, anyways 🦀:1
Footnotes
|
I know the RFC says "Path inference", but I think this really only true if you take into account the "future work" section, like the What the bulk of the discussion is around is basically just a specialization(shorthand) around type inference. And then from there the talk is about what best way to represent this in syntax. Reusing existing rules, with
I mentioned before, at least from what I could find in the C# case, the choice of I think we can acknowledge that other languages use it, and have similar goals for their proposals, providing shorthand, but beyond convergence, I think its not really productive to latch on to what other languages did, and especially to try to use that as a negative for what rust wants to do with its own syntax. We just need to find what works best. As for If variant types ever become a thing, I also would rather a enum Foo { Bar, Baz }
fn foo<F: Foo>() { ... }
let foo = foo<::Bar>();
// or
let foo = foo<_::Bar>();
// vs
let foo = foo<.Bar>();I know this is not assured to be a thing, but I think we should try to be broad minded were possible. With these simple enums, for example, this could also potentially be done with const generics, if it got expanded to them, which could have a higher likelihood of being done. But regardless, whether as a generic type or as a generic value, this example demonstrates the "specialized type inference" aspect I think this current RFC is really about. I don't mean to hijack what this RFC is for with my own interpretation, but wanted to at least give my point of view on it. I think "Path inference", for the real guts of what this is introducing, leaving alone future work, is a bad name.
Interesting, as I had thought the opposite. With structs, specifically let connection = Connection::open(.{
host: "localhost",
port: 5672,
username: "user",
password: "bitnami",
})
.await
.unwrap();This configuration pattern is used now, but its much more verbose, and often to provide a "nice" experience, the use of some With the proposed and accepted default fields syntax, you could have the let connection = Connection::open(.{
host: "localhost",
port: 5672,
username: "user",
password: "bitnami",
..
})
.await
.unwrap();If there was a "partial default" support, where some fields become required (borrowing a c# implementation here, but rather than using positive space like adding a keyword we could use negative space), then this would add more versatility to the constraints for this config pattern (rather than needing to provide a function to enforce these required fields). In the above example, host, port, etc. would not be default, and would therefore be required to provide (by means of its negative space of not being default). And for the rest, if you don't need anything special, just becomes So in my mind, for both short term, having "named parameters" + the interaction with default fields, and long term, adding support for partial defaults in objects, adds more value overall, especially in user facing code, than what enums would get from this. Though maybe I am just missing something obvious here and am not accounting for enum shorthand enough, or what other variant type/const proposals could bring that would promote the use of the shorthand even more. In all, I am happy this RFC is getting a good amount of discussion, as this is something I think could really promote different design patterns and would love to see this added to the language. |
|
Also forgot to mention, with the (this would be different from zig, for example, as all values there have defaults by default, so nothing is required(compile error) to provide ( It brings in other language concepts, but done in a more sane way (at least to me), and this could honestly be my summary of rust as a whole. This is more speculation(apologies), but I think a holistic approach is good here, as you can see the sum being greater than the parts. (this is all to say that I dont think structs should be left out of this RFC) |
Why? I get the appeal of a single sigil, but single sigils are a limited resource - why is it so important for this particular feature to get one?
Regardless of what gets implemented first - assuming anything here gets implemented at all - I think we there is still merit in not compromising the design of the low-priority items without a good reasons. |
|
I'd feel more comfortable with All that said, I would be happy to have this feature with any of the proposed syntaxes. |
i wonder if there is any ambiguity to reuse |
The let connection = Connection::open(.{
host: "localhost",
port: 5672,
username: "user",
password: "bitnami",
...default(),
})
.await
.unwrap();Looks a bit cool to me, but might be confusing for new comers. With the first two The next I would rather see this as a diagnostic auto fix tool or within rust-analyzer, given that nowadays writing code is fast with llm and reading code is the more time consuming part, the cons outweigh the pros given the flexibility IMHO. |
If they are associated constants for the type itself, then it's impossible to infer them because any type can have associated constant with any type. This means that the compiler must scan all types, looking for any type that has a constant with that name that has the desired type, and take it. If there are multiple such types - then it fails. The only way to infer them is if they are defined by a trait. Searching for a trait that implements a given member is something Rust already does, and if the trait defines a constant that uses But... Rust already has syntax for type inferences based on trait members' types - Footnotes
|
As mentioned in the quoted comment "With the proposed and accepted default fields syntax" that
Since |
|
I would find the struct literal case immediately useful in macro implementations. I have macros that generate bidirectional mappings between two types -- think Ideally, the macro could generate expressions like |
I think this feature will be used a lot if added, and worth the dedication to it. I think it would be simpler to remember rules of a new single sigil when the intent is for this type inference. All the context you really need for the use is an indicator of the intent for this inference. Same reason I would imagine a single For example, having some places that have For some formatting issues I have: fn foo(_ {bar, baz}: BarBaz) {}Or in the nested case above: T { foo: _ { bar: _ { baz: ... } } }These look less succinct compared to: fn foo(.{bar, baz}: BarBaz) {}
T { foo: .{ bar: .{ baz: ... } } }We already have Like combining: fn foo(_ {bar, _ { foo: _ { bar: _::Variant { baz: ... } } }}: Thing) {}
fn foo(.{bar, .{ foo: .{ bar: .Variant { baz: ... } } }}: Thing) {}To my eyes, its clear which reads better. I think it conveys the actual intent wanted better as well. Words are escaping me on how I should best explain my overall view here on Its like while
If this doesn't make sense then feel free to ignore. Its not meant to be taken exactly, so I only mentioned the parts where it matches what I am trying to convey. I plan on thinking more about the sigil and trying to give a more technical breakdown when I get a better idea of how to convey it. Part of this will include how syntax highlighting would be different, like how for an enum, Things like text search are interesting to think about, and actually having the different types of And thinking on alternatives, this actually brings up an interesting question: If anyone has context why it was decided to move away from |
Yeah, on thinking more, I would agree, the solution should be found for all of the related types in this single RFC for a more cohesive feel. And in thinking on enums, I had thought of this example, where the let connection = Connection::open(_::Https {
host: "localhost",
port: 5672,
username: "user",
password: "bitnami",
..
})
.await
.unwrap();Where as this seems much cleaner: let connection = Connection::open(.Https {
host: "localhost",
port: 5672,
username: "user",
password: "bitnami",
..
})
.await
.unwrap();I definitely don't want to have different syntax for the use sites with enums, like some places use |
I may be leaning too much into the metaphor - but it's not an electron. One important property of electrons is that they are all the same. All electrons have the exact same physical properties (same size, same mass, same charge, same spin). But here these expressions are resolved to different types, so it's more akin to atoms which can have different types1. The way I imagine the electron metaphor is that something like This is not how it will work, because Rust has no such magic ad-hoc struct type. With atoms, you can't just say "this is here is an atom" - you need to tell the compiler which type of atom. This is what you do in the current syntax, where you have to specify the type - Footnotes
|

This RFC proposes a leading-dot syntax for path inference in type construction and pattern matching. When the expected type is known from context, developers can write
.Variant,.Variant { … },.Variant(…),.{ … }, or.(…)instead of typing the full type name.Rendered