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


#1

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.


Arc::new(Mutex::new(0u32)) -> Arc(Mutex(0u32))
#2

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.


#3

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.


#4

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.


#5

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


#6

Seems ok to me, personally.


#7

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.


#8

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.


#9

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.


#10

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


#11

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.


#12

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.


#13

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


#14

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)


#15

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.


#16

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


#17

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?


#18

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


#19

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


#20

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