-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Inferred types _::Enum
#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?
Inferred types _::Enum
#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 |
text/0000-infered-types.md
Outdated
[unresolved-questions]: #unresolved-questions | ||
|
||
|
||
A few kinks on this are whether it should be required to have the type in scope. Lots of people could point to traits and say that they should but others would disagree. From an individual standpoint, I don’t think it should require any imports but, it really depends on the implementers as personally, I am not an expert in *this* subject. |
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.
I think this question needs to be resolved before the RFC is landed, since it pretty drastically changes the implementation and behavior of the RFC.
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.
I would love to discuss it (:
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
|
@SOF3 Yeah that's fair; on second thought I retract my opposition. That being said I do like my explicit types, so if this goes through I personally will likely refrain from using it. |
This may be difficult to distinguish from the unnamed match-all math Foo {
_ { x: 1, y: 2 } => { ... },
_ { x: 2, y: _ } => { ... },
_ => { ... },
}
let _ { x, y } = foo;
let _ = bar; Currently, While it might be considered more concise, I believe it hides too much and makes the code harder to understand -- especially for people unfamiliar with the codebase -- without adding much more value than having to type a few less characters. |
is it possible infer this way? match dir as Direction {
::North => { .. }
::East => { .. }
::South => { .. }
::West => { .. }
} |
@igotfr That would be ambiguous with other meanings of |
Hiya, @kennytm made a note in the discussion I originally posted in that this is the RFC to be in, so I'm duplicating this here for ease of access :) Just a thought, but what if this form of syntax were to be introduced: match dir using Foo::Bar::Direction {
North => { ... }
East => { ... }
South => { ... }
West => { ... }
} where
However, this comment on #2830 brings up the very valid point of match statements that destructure composite types, and hence different (possibly conflicting) type-names. match (fruit, company) using (
fruit: Foo::Fruits,
company: Bar::Companies
) {
(Apple, Google) => { ... }
(Orange, Samsung) => { ... }
(Durian, Apple) => { ... }
_ => { ... }
} This is another reason I would prefer a keyword like I might be very wrong, but as far as I can tell, any super deep nesting would require either:
Well, these are my two cents on the matter (ok, a bit more than two). Like most other programmers I dislike writing more code than I need to, but I also appreciate both explicit and strong typing, and the power that that affords match statements; thus, I feel that being able to scope a type just where you need it would be a great solution to the verbosity. This is an idea that borrows from a great many other ideas, but I would love to see something added to the language to address this, either way. Amendment: it has occurred to me now that it would perhaps be quite annoying to implement such a complex new syntax, what I wonder is if it would not be possible to simply allow type annotations within the match (fruit, company): (Foo::Fruits, Bar::Companies)
{
(Apple, Google) => { ... }
(Orange, Samsung) => { ... }
(Durian, Apple) => { ... }
_ => { ... }
} These type annotations (or something similar) should be able to afford the compiler the right amount of information, while still being quite simple and familiar. |
I don’t like it. You have to know what you’re writing before you write it to use this syntax.
2025年1月14日 +0800 19:17 Lemon ***@***.***>,写道:
… Hiya, @kennytm made a note in the discussion I originally posted in that this is the RFC to be in, so I'm duplicating this here for ease of access :)
Just a thought, but what if this form of syntax were to be introduced:
match dir using Foo::Bar::Direction {
North => { ... }
East => { ... }
South => { ... }
West => { ... }
}
where using implicitly scopes Foo::Bar::Direction::*, for ease of use?
• I don't think people will mind if the line with match gets a little longer
• This allows for narrower scoping than a preceding use statement
• I'm impartial to the exact keyword used, but using is somewhat akin to use and so identifies the usage more clearly?
• @berkus brings up the possible use of in instead, which I also find quite intuitive
• I personally don't like the keyword as for this use-case, since as tends to be reserved as a keyword for renaming things
However, this comment on #2830 brings up the very valid point of match statements that destructure composite types, and hence different (possibly conflicting) type-names.
For which I'd like to offer an extension to the syntax, to mirror type-specification in function declarations:
match (fruit, company) using (
fruit: Foo::Fruits,
company: Bar::Companies
) {
(Apple, Google) => { ... }
(Orange, Samsung) => { ... }
(Durian, Apple) => { ... }
_ => { ... }
}
This is another reason I would prefer a keyword like using, because it lends itself well to pluralisation.
But, I do understand that it's probably annoying to have to add a new keyword to the lexer, especially with this strange extended syntax that the parser will have to make heads or tails of.
Please see Amendment, for revised thoughts
I might be very wrong, but as far as I can tell, any super deep nesting would require either:
• manual construction of a complicated scrutinee
• in which case, the one that constructed that scrutinee can also construct a complicated type specification :)))
• existing types to pull from
• which the compiler should(?) be able to infer, since those types will have to have been explicitly defined somewhere
Well, these are my two cents on the matter (ok, a bit more than two). Like most other programmers I dislike writing more code than I need to, but I also appreciate both explicit and strong typing, and the power that that affords match statements; thus, I feel that being able to scope a type just where you need it would be a great solution to the verbosity. This is an idea that borrows from a great many other ideas, but I would love to see something added to the language to address this, either way.
And for anyone that's read all my ramblings, thank you, I appreciate it, have a lovely day ^-^
Amendment: it has occurred to me now that it would perhaps be quite annoying to implement such a complex new syntax, what I wonder is if it would not be possible to simply allow type annotations within the match line, e.g.
match (fruit, company): (Foo::Fruits, Bar::Companies)
{
(Apple, Google) => { ... }
(Orange, Samsung) => { ... }
(Durian, Apple) => { ... }
_ => { ... }
}
These type annotations (or something similar) should be able to afford the compiler the right amount of information, while still being quite simple and familiar.
The points I made above about more complicated scrutinees still hold true here, I'm just modifying the syntax a little.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
Interesting, please forgive my confusion, but could you please give me an example of when you would write code that you didn't know the type of? Also, as a note, I'm not asking for this explicit typing to be enforced in all let fred = (fruit, company);
match fred: (Foo::Fruits, Bar::Companies) {
(x, Google) if x != Apple => { ... },
(Orange, Samsung) => { ... },
y if (<y condition>) => { ... },
_ => { ... }
} This would also be fine, using match guards to filter things out, since the exact type of the value being passed is known, if the type can be destructured, the compiler will be able to type its constituents, too, no? Please do let me know if I'm missing something glaringly obvious. |
I think "didn't know" is too strong. The way I see it, this feature is for cases where fully writing the type will be cumbersome and redundant to understanding the code. Here is a simplistic example: use std::collections::hash_map::Entry;
use std::collections::HashMap;
fn main() {
let mut hashmap = HashMap::<usize, usize>::new();
for (i, num) in [1, 2, 3, 2, 1].into_iter().enumerate() {
match hashmap.entry(num) {
Entry::Occupied(entry) => {
println!("Alreayd seen {num} at index {}", entry.get());
}
Entry::Vacant(entry) => {
println!("Inserting new entry for {num} - index {i}");
entry.insert_entry(i);
}
}
}
} The important type here is Does spelling out |
why don't just to use |
This has also long annoyed me. I find it should be possible to put declarative statements at the beginning of every block, like: match (fruit, company) {
use Foo::Fruits::*;
use Bar::Companies::*;
(_::Apple, Google) => { ... } // Hmmm, ambiguous example, but that’s probably rare.
(Orange, Samsung) => { ... }
(Durian, _::Apple) => { ... }
_ => { ... }
} I came here after having felt a need for generalised fn f_unit() -> SomeLongnamedUnitType {
// SomeLongnamedUnitType
_
}
fn f_struct(x: f32) -> SomeComplicatedStruct<Type> {
// SomeComplicatedStruct::<Type> { x }
_ { x }
}
/* Edit: commented out, because this is actually pretty much a function, not a type syntax
fn f_tuple(x: f32) -> SomeComplicatedTuple<Type> {
// SomeComplicatedTuple::<Type>(x)
_(x)
} */ I guess the objective should not be to squeeze it into every edge case. Instead, accept it only where there is unambiguously only one type that |
I don't think that |
Both
"More verbose" does not automatically mean "more readable", and using a word instead of a symbol is not enough - the chosen word also has to have the correct meaning. The reason why Consider this expression: iter_stuff()
.flat_map(|item| {
if let _::Variant(payload) = item {
Some(payload)
} else {
None
}
})
.collect::<Vec<_>>()
|
Yeah, I'm not too sure on the feature itself, but if we do want to add this, this seems like the correct syntax. |
const Variant: Enum = Enum::Foo(3);
#[derive(PartialEq)]
enum Enum {
Foo(u32),
Variant,
}
impl Enum {
fn printit(self) {
match self {
Enum::Variant => { println!("Enum::Variant") }
self::Variant => { println!("self::Variant") }
Enum::Foo(x) => { println!("Enum::Foo({x})") }
}
}
}
fn main() {
Enum::Foo(3).printit();
} the above runs the self::Variant branch |
What about it is not good? Just curious. |
Addressing previously made pointsTo address @igotfr's point about the use of other keywords, the
To address @daniel-pfeiffer here, I feel that putting the generic Underscore and Colon SyntaxesTo speak to the response from @idanarye: you make a very good point about the existing meaning of the Nonetheless, there are 2 reasons I still prefer the type specification syntax: A less important reason is that it moves the extra syntax from the match arms to the match head (?); that way, the extra verbosity is concentrated in one place, and doesn't need to be repeated, i.e. bite the bullet declaring your type at the top, then don't worry about the syntax after that. The much more important reason, is that the syntax here serves 2 purposes:
That is to say, by re-declaring the types you're working with, just above a complex operation involving those types, it makes it easier to understand the code without flitting back and forth; seeing Naturally, if one didn't want to write out a type (or sub-sections for a more complex type), it would make sense to allow To add to that, the colon syntax is very specific to match-like constructs, whereas I can see the underscore being very useful in a lot of places, described by many comments in this RFC, back in 2023 and early 2024. Of course, regarding the compiler, I imagine that the Match Statement Debate Overview (to the best of my knowledge)Just to give a full example: // import this enum from somewhere
use idk::Bar; // use idk::Bar::{operation, ..., Companies}; // may be better
// define this enum here
enum Fruits {
Apple,
Orange(u32),
Durian,
Cantaloupe
}
let fruit: Fruits = { ... };
let company = { ... }; // some operation related to imported `Bar` module
let fred = (fruit, company); The existing syntax (without workarounds like scoped imports): match fred {
(Fruits::Apple, Bar::Companies::Google) => { ... }
(Fruits::Orange(x), Bar::Companies::Samsung) if x < 7 => { ... }
(Fruits::Orange(_), x) if !matches!(x, Bar::Companies::Samsung) => { ... }
(Fruits::Durian, Bar::Companies::Apple) => { ... }
_ => { ... }
} This is the underscore syntax (I believe?) that has been suggested: match fred {
(_::Apple, _::Google) => { ... }
(_::Orange(x), _::Samsung) if x < 7 => { ... }
(_::Orange(_), x) if !matches!(x, _::Samsung) => { ... }
(_::Durian, _::Apple) => { ... }
_ => { ... }
} This is the colon syntax that I'm suggesting: match fred: (Fruits, Bar::Companies) {
(Apple, Google) => { ... }
(Orange(x), Samsung) if x < 7 => { ... }
(Orange(_), x) if !matches!(x, Samsung) => { ... }
(Durian, Apple) => { ... }
_ => { ... }
} To note, tuple variants under underscore syntax, like If we get into the point that only ambiguous variants should use I think the heart of the problem is that we're happy to have imported things beforehand, and to have properly qualified our types, but not to keep qualifying them again and again in match arms. This RFC and ImplementationOne benefit of all these suggestions is to reduce glob imports, which @JoshuaBrest mentions near the start of this issue. I agree with both sides, but side more with @JoshuaBrest:
I (also?) agree with the notion that the underscore syntax is a good idea, but I do prefer the extra (and more spatially localised) information conveyed to the reader by the colon syntax, which I think is the main benefit of the non-underscore syntaxes that people are suggesting. P.S. sorry for the massive essay, I'm just hoping to summarise some of the key discussions here Footnotes |
Why would the compiler need to "focus"? It's not a human - it does not have a limited attention span that needs help limiting itself to the important things.
The reader, on the other hand, does need to focus. And so does the writer. I think, in a match statement (TBH - pretty much in any place where this feature is relevant) the type is not what one needs to focus on - it's the variants. Just like omitting the type when you use combinators (where would one even write the types when using combinators? EXACTLY!) doesn't hurt readability, so does inferring the types in these cases.
I'm confused - wouldn't omitting the type there mean you have to write it in every branch of the
I don't like this syntax for the following reasons:
This is already supported - e.g. one could use
There is no ambiguity because the
I'm not happy with either of these things. I don't want to pollute the namespace with something the compiler can easily infer for me, and more importantly - having to needlessly acknowledge types decreases the power of macros (which can't always determine the type) |
afte type-ascription got nuked from orbit (#3307) I'm quite certain the |
Colon and Underscore Syntax FlawsReadability is partially about how quickly a reader can understand code; reducing effort on the writer's side generally increases the effort on the reader's side, and rust is a language that's designed for maintability and sacrificing write-time (be that for run-time or for read-time).
Nothing represents the variants, the whole enum is 'brought into scope', which is in line with the way that
Firstly, I did admit that the I don't mean this in an antagonistic way, but could you really tell me if you saw: match fred {
(_::Orange(_ { size, firmness }), _) in an unfamiliar codebase, that it would be easier for you to understand than: match fred using (Fruits, b::C) { // qualifying:
(Orange(Orange { size, firmness }), _) The former tells me that there's an Moreover, if I want to find its definition, I'd know that
Though the compiler would likely be fine with either, as the reader, you understand what's going on much quicker with the latter syntax.
This is a good example, I don't particularly like that pattern, but if it is part of the language, then my previous point is rendered moot.
Thank you @kennytm, I wasn't aware: there are good points in there. Independently, I've also realised another problem with the colon syntax: it is a lie, i.e. it's misleading as to what is happening under-the-hood. Proposing regression to
|
The way I see it, readability is not about info-dumping as much as possible on the reader. Omitting information only hurts readability if the omitted information is required for understanding the code (the flow of the code - not every little decision the compiler is going to make when compiling it) My argument is that in cases handled by this feature1, this information is not required. I'll explain with the example you gave:
In both versions, I know that I'm handling the No version tells me what the The second version tells me that there is a struct named match fred using (Fruits, b::C) { // qualifying:
(Orange(orange), _) => { ... } Because then we could do
Footnotes
|
Ideally, I think the solution for this should work in all pattern contexts, not just match expressions. That would include things like The |
I don't think the if let Orange(orange) = fruit using Fruits {
...
} Is better than this: if let Fruits::Orange(orange) = fruit {
...
} |
I wrote this PR when I was much younger and more naive. Looking back, I realize there are definitely areas that need revision. One major concern I’ve been reflecting on is how we resolve the base type when using This is a pretty fundamental shift from how Rust usually operates. It creates a case where types are being used without a visible import path, which could affect both readability and tooling support. For example, it becomes harder to track type usage by grepping for its name or relying on IDE tooling, since One possible solution is to require that the type behind These are just some of my thoughts. I don’t have a fully formed solution yet, but I’d love to hear your input so I can revise the RFC accordingly. |
Rust can already do that - e.g. with |
Whoops! You’re right! Yet another reason why this change is not problematic! |
Doesn't this have the exact same problems as I also don't like the syntax, firstly I think |
This RFC is all about allowing types to be inferred without any compromises. The syntax is as follows. For additional information, please read the bellow.
I think this is a much better and more concise syntax.
Rendered