This is a bit of a bikeshed, and it’s really late to raise this question (I’ve been stewing on it for a while). But I feel like our constructor convention of using an associated new fn is a bit longer than necessary and inconsistent with tuple structs and enum variants. I am curious what people think of the idea that the “default” constructor for a type Foo just be a fn called Foo.
Cast your votes:
I like it!
I hate it!
Some examples:
Vec::new() becomes Vec()
Rc::new() becomes Rc()
and so on.
Advantages:
Fits with many modern languages (e.g., Python, I think Ruby, I’m sure more)
Fits with tuple structs and enum variants.
One use statement gets the type and the constructor (as today)
Disadvantages:
Churn, but we are doing a lot of “convention setting”
Requires creating fns that don’t fit the snake-case naming convention
Anyway, this is kind of just a poll to test the temperature out there.
I like this idea – given that we already break the snake case convention for tuple struct constructors, this seems OK.
It will mean that the non_snake_case lint would need to be tweaked, though. I’m not sure if we could make it smart enough to pick up on this convention.
I feel like that's a secondary concern, but it seems like it shouldn't be too hard -- just look for a type defined in the same module with the same name.
Historical note: this was the convention for constructors in Rust long, long ago.
Originally, when we moved to Foo::new(), one of the rationales was that Rust’s lack of default arguments will make it common to have multiple different constructor functions, such as Vec::with_capacity(). Is it worth “blessing” one constructor function even more?
Otherwise, I do sort of like it. The fact that enum variants are already functions-pretending-to-be-types is convincing, and reducing usage of :: is a noble goal.
I’m actually quite fond of the difference that exists now. Having Foo(...) being used mainly and idiomatically only for tuple types is quite informative in code. It tells me that I can match it the way it was constructed, which wouldn’t work for constructor functions. In general, I tend to think of Foo(...) as a value construction like Foo { ... } and of the various constructors as actual delegation of construction.
I find Foo::new(...) also more in line with the fact that there are other constructors (from_xyz, with_xyz).
I would also think that this could introduce another special case for macros to handle (for example when passing a constructor name).
Might it be possible to make static methods importable as functions? So you could do
use foo::Bar::new as Bar;
use foo::Bar::with_capacity as CapBar;
...
let a = Bar();
let b = CapBar();
Then you could save the typing for the ones you use often, and you have a chance to see the imports and know about them when opening a file (I personally tend to look at the imports first in a file I haven’t seen before).
To me Foo(x, y) and Bar {x: y} have come to indicate plain old data. I’m quite happy with ::new and ::new_with_, knowing that these functions probably do some initialisation work (or may do in the future).
I also like the un-focus on OOP à la C++/Java that Rust has shown so far.
I think there are good points in here to potentially justify the asymmetry with enum and tuple constructors, in that these cannot have custom logic and can be used as patterns.
Something I really like about Rust is that it doesn’t add new syntactic sugar for every little thing, so I’m personally a big fan of Foo::new being just another function. Less special syntax to learn, and fewer cases in my mental parser. Fewer edge cases, too - as others have mentioned, having a few different constructors with explicit names isn’t uncommon, and I’d prefer them all to look similar for the writer and the reader.
I prefer the look of Foo(), but I am persuaded by the argument that ‘constructor-looking’ things should be destructurable in pattern matching, so on balance I prefer Foo::new()
I really don’t understand why we keep trying to add more and more syntactic sugar for things. Can we at least wait until 1.0 before we make backwards-incompatible changes to save a couple of characters which don’t actually add additional power to the language?