-
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 |
If the |
Just so there is no confusion, this is not my RFC. I provided other examples of languages adding similar shorthand. The RFC itself states that swift is the main inspiration. Didn't mean to add confusion here. Sorry. And this is just my own interpretation, but I believe the idea is not to intentionally copy the exact implementation of other languages solely because they do it too, the original idea was to use For example, If the main hangup is the chosen sigil, then we can try to brainstorm others (we already cannot use a The
I believe (I haven't kept up)
This is exactly the reason I mentioned those other languages to point out that the other language designers looked at |
It was already discussed multiple times here, but given the sheer amount of comments I'm not expecting anyone to read everything so it's worth bringing up again: The point of
Footnotes
|
Swift, too, uses the |
|
I'm not sure if the reponse is for general clarity, or if I stated something that would suggest this and this had to be clarified. So apologizes if below doesn't need to be said. From my response:
i.e. the I didn't mean to imply the use in the discard pattern. I meant that the The main issue with In pattern matching this can lead to: enum Foo { bar, baz }
let foo = Foo::baz;
match foo {
_baz => {},
_bar => {},
}Is this captured variable? Is it a shorthand qualifier for the variant? This is an ambiguity, and a pretty hard one. And I'm pretty sure is a breaking change regardless. It makes sense to me that why although |
|
And again - this is not a new meaning for |
I couldn't find the initial proposal for the implicit member expression shorthand in swift, but from looking at the enum Coin {
case heads, tails
func printMe() {
switch self {
case heads: print("Heads") // no leading dot
case .tails: print("Tails") // leading dot
}
if self == heads { // no leading dot
print("This is a head")
}
if self == .tails { // leading dot
print("This is a tail")
}
}
init() {
let cointoss = arc4random_uniform(2) == 0
self = cointoss ? .heads : tails // mix and match leading dots
}
}
Right, I used We could just make all syntax, regardless if disambiguation needs, use this: enum Foo {bar}
let foo: Foo = _::bar; // Already clear here
struct Point(u32, u32);
let point: Point = _::(0, 0);
let connection = Connection::open(
_::{
timieout: 1000,
retries: 10
}
);If having a single |
No, because you don't do
In all these cases - including the first, which is an existing syntax and works exactly the same as the suggested ones after it are supposed to work - |
|
I think support for use std::marker::PhantomData;
struct PhantomTuple<A, B>(A, PhantomData<B>);
fn main() {
let _tuple = PhantomTuple::<char, f32>('Q', PhantomData);
}Although a little absurd, you would need to handle some kind of: // Could also have `_<>` though this is a change from the goal of
// using `_` purely for the type in the syntax, as if done that way,
// it would need the `::`.
let _tuple: PhantomTuple<_, _> = _::<char, f32>('Q', PhantomData); For comparison, with just a dot this would look like: let _tuple: PhantomTuple<_, _> = .<char, f32>('Q', PhantomData);This is partially (similar family of issues) addressed in the RFC in the unresolved section. In general, I would say that To me, this RFC has the most value in creation contexts, not in pattern matching contexts. It would most heavily impact enums if only creation is supported, but if just for creation where the type can be inferred, it might be fine to have some form of If done this way, then there could be a clear distinguishing between inferring and creating as well, to help decide if For future work, there is the possibility of variant types (or maybe now this is incorporated in pattern types?), and the shorthand here could probably go along ways in the ergonomics of the use there. I'm not sure how far along things are in any direction, but its something we must keep in mind as not wanting to conflict and block from acceptance. Overall, I would say I fall in the camp of wanting the idea, but would prefer a single sigil syntax over a mix, where although there are existing rules that this fits logically, just don't do enough in all contexts to justify the extra ceremony done just to keep up with the rules in all those contexts. And as said before, potentially limiting this RFC to just creation, and forbid in patterns, might also make the most sense, as you can argue there should be more context needed in those spots to best serve its purpose. This can prevent some obvious destructing examples though, like: struct BarBaz {bar: i8, baz: bool}
// Or `_ {bar, baz }`
let .{ bar, baz } = get_bar_baz(); // returns `BarBaz`or in functions: // Or `_(params):`
async fn query(.(params): Query<HashMap<String, String>>) {}But that might be the trade-off(or again, maybe even desired) to make. |
I actually agree that the "one rule to rule them all" story for Once I started writing code with the leading dot syntax, it felt a lot nicer in practice than the underscore forms. It reminds me a bit of how the In Rust, a leading As @RoloEdits,
I'm actually not planning to support generics in inferred syntax. |
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 reference", 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. |

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