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

Argh. I always forget that Discourse requires top-posting. All I was saying is that the original proposal involved no syntactic sugar.

How are namespace conflicts handled, then? You often do want to use static methods on types other than the original constructor; would you have to use an alias and keep the function in its own module? Is that really simpler or easier, or am I missing a way in current Rust that this would be clean to resolve?

Under this proposal, how will this code work?

struct Foo(pub uint);

impl Foo {
    fn new(n: uint) -> Foo {
        Foo(1 << n)      // <-- ?
    }
}

let foo = Foo(8);     // <-- ?

This proposal doesn’t alter any language semantics, just conventions. So that example will operate in exactly the same way that it does today.

There appears to be a lot of confusion along these lines, so I’d like to clarify how I understand this proposal with an example. The current convention for the ‘main’ constructor function of a type is to define a static new method on it:

struct Foo {
    x: int,
}

impl Foo {
    fn new(x: int) -> Foo {
        Foo {
            x: x,
        }
    }
}

Under this proposal, the convention will be changed to defining a method in the same scope with the same name as the type:

struct Foo {
    x: int,
}

fn Foo(x: int) -> Foo {
    Foo {
        x: x,
    }
}

This is doable today, and is in fact used in some places (particularly in rustc itself, where conventions are somewhat ignored); for example, syntax::ptr::P uses this convention.

In that case a tuple-like struct cannot have such “constructors”.

struct Foo(uint, uint);
fn Foo(n: uint) -> Foo { // ← error: duplicate definition of value `Foo`
     Foo(n, 1 << n)
}

True, my view is that a tuple-struct already has a default constructor, and if you want a different one, you should either use a struct with field-names.

1 Like

It annoys me that constructors in C++ don’t have easily searchable names (since obviously the class name gets used many times), besides which it isn’t obvious in a few cases that the constructor is being called. Typing a few extra letters to make that explicit with consistent notation is definitely worth it in my opinion.

4 Likes

Far, Far prefer this. it’s fine to have named functions for ‘alternative’/‘special case’/‘complex’ constructors, but I’ve always found ::new() to be redundant, unnecessary ugliness that stabs me in the eye. I used tuple-structs for vector maths purely because I dislike the idiomatic Vec3::new(x,y,z) convention. This proposed convention would allow you to move back & forth more easily. This is the kind of pointless refactoring issue we’re trying to escape from C++. “if you reverse this choice later, you have to change calling conventions …”

You can currently write struct Foo{} fn Foo()->Foo{} , and it is unambiguous both at call & definition site, hence still easy to search for. (grep struct Foo{ vs struct Foo( vs fn Foo vs Foo( vs Foo{ )

Don’t “Throw out the baby with the bathwater” when comparing to C++: C++‘s system is difficult because of many combined factors: header files, everything is overloaded, there are hidden conversions, and its hard to distinguish a definition from a use in a single line. Rusts’ lack of headers & simpler grammar (especially introducing types with “:” ) cut through many problems.

box Foo::new() also looks very redundant.

its’ also repurposing of the word ‘new’ compared to other languages which might cause confusion for people creating cross-langauge C wrappers.would people expect extern “C” Foo_new() to really be a function that allocates and initialises, returning Foo* ? opposite is Foo_delete(Foo*) ? Rust doesn’t exist in isolation.

Are there any details in language support, for example does use foo::Bar get all symbols ‘Bar’ from foo (both the struct and the fn,if you make one)

I totally misunderstood the original proposal. If it’s just about conventions, then my answer would be to not default to either, but to just choose whichever makes sense for your actual struct (or something else entirely)… For little things like Vec3, I’d definitely agree this seems nicer.

This makes it clash with tuple struct constructors though. I prefer Foo::new() for user defined constructors, and Foo() for compiler provided tuple-like construction for variants/tuple structs.

1 Like

I would strongly vote for consistency. As bstrie already wrote: Why should ::new be special compared to ::with_capacity?

Reminder: If this change comes, it also has to be ensured that &Foo returns a function pointer as &Foo::new does

1 Like

My main problem is that it breaks the style. I don’t really like Rust’s default style, but at least it’s consistent.

I could live with Foo::foo() or non-method foo().

::new() is a huge improvement in consistency then introducing the idea of constructors. -1

3 Likes

Type::new() is one of the things that I immediately liked about Rust. It shows that the constructor is not special or magical in any way; it is just a function. Of course, when the constructor is a free function this is still the case, but this is not immediately obvious, especially for people coming from C++/C#/Java, where the constructor is special. If Vec() would be the default constructor for Vec, then it could also be confusing that Vec::with_capacity(c) is a constructor as well. It might be more consistent with tuple structs and enum variants, but it is less consistent with non-default constructors.

The five extra characters do not bother me at all. Rust is already quite concise (e.g. Rc instead of C++’s shared_ptr).

10 Likes

&Foo won’t produce a function pointer, but a reference to a function pointer - assuming fn Foo(...) -> Foo {...}, which is possible today, even if a type Foo is declared in the same module. That said, Foo is no different than Foo::new, or any other free function or static method.

I real like the fact that rust does not have special constructors but just static methods witch initialise the object on POD basis.

Also the initialisation with Enumval(...) and Foo {...} shows clearly that it’s a POD initialisation where Foo::new(..) might do other stuff like data transformation, validity checks or even mutating mutable constants.

So -1 for this idea. Also the current convention is much clearer in regret to additional constructors like from_…

1 Like

As far as I can tell, at least in my own code, you would write

struct Foo *foo_alloc() {...}
void foo_free(struct Foo *foo) {...}

not

struct Foo *foo_new() {...}
void foo_delete(struct Foo *foo) {...}

new and delete just feel very "C++", not C. C uses malloc and free.

I much prefer Vec::new() over Vec(). I dislike method overloading, and I dislike using the type name for the constructor name in C++. Rust seems to get it right, to me. I also love the searchability of ::new(. I can find many ctor calls just by searching for that string, unlike C++.

In C++, because you can’t use a different name for ctor overloads, you are sometimes forced to add extra unused parameters to ctors, just so that you can abuse method overloading in order to choose the overload that you actually want. In Rust, you never have that problem.

Keep it the way it is.

3 Likes

In case it’s unclear, no such change is happening now that alpha has been released.

5 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.