Poll: `Foo::new()` vs `Foo()` as the default constructor

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.

1 Like

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.

1 Like

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.

1 Like

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).

8 Likes

Seems ok to me, personally.

In Ruby, Foo.new() makes a new Foo, and Foo(bar) casts bar to a Foo.

This is by convention, it’s actually juts a method like any other.

I think that new is already blessed as the “normal” or “default” constructor, so I don’t think this proposal changes things on that front.

I like the symmetry of foo::new being just another static constructor function. That said it has some ergonomic penalties.

This would make Vec() more similar to vec![]. The latter-of-which seems to be used and recommended universally as more convenient.

1 Like

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).

1 Like

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.

14 Likes

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.

1 Like

I agree with the constructor function symmetry sentiment, and that tuple struct “constructors” are POD.

On the other hand, I think you could still (sort of) follow the snake case rule by making Foo(x) sugar for Foo::new(x).

1 Like

Personally, I think having Foo::new() made alternative constructors much more discoverable. At least that’s how it feels when I am learning Rust.

Having Foo() means that people will have to look for constructors in two places/forms instead of just one.

(other than that I don’t have a strong preference either way)

5 Likes

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.

4 Likes

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()

1 Like

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?

5 Likes

On Thu, Nov 06, 2014 at 07:52:03PM +0000, Ben Foppa wrote:

On Thu, Nov 06, 2014 at 08:36:17PM +0000, Pythonesque wrote:

@nikomatsakis your replies were eaten! Mind re-sending?