Initializer Syntax Documentation Location?

Where do I learn more about

  • Different initializer syntax supported by different types
  • If possible, then how, to implement a custom initializer for a type

Learned about them close to end of the first section in Chapter 19 of the Rust Book and wanted to dig deeper.

I checked here to see if it was an operand that can be overloaded but didnt find anything. Looking at the page for std::vec::Vec wasn't helpful either.

There're only 2 ways to construct a value of certain type.

  1. To mimic how the type is declared. To do this you need have access to all its fields in this scope. By default struct fields can only be accessed within the module, which prevents them to be constructed in this way outside from the module it is declared.
  • struct Foo { a: i32 } can be constructed from Foo { a: 42 }
  • struct Foo(i32); can be constructed from Foo(42)
  • struct Foo; can be constructed from Foo
  • enum Foo { Bar { a: i32 }, Baz(i32), Quux } can be constructed from Foo::Bar { a: 42 }, Foo::Baz(42), and Foo::Quux.
  1. To call a function which returns the value. The function can either construct it directly or call another function.

The closest thing to a constructor operator in Rust is the Default trait. If you can make an instance of your type with no outside arguments, it usually makes sense to implement it, or to derive it (as shown in the link).

I am talking about something like below (taken from the Rust Book - Chapter 19.4)

fn main() {
    enum Status {
        Value(u32),
        Stop,
    }

    let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
}

Tuple types and tuple variants automatically get a constructor function defined. So we can rewrite that example:

fn main() {
    enum Status {
        Value(u32),
        Stop,
    }

    // The tuple variant constructor is a function that takes the values
    // and returns the enum type
    let f: fn(u32) -> Status = Status::Value;

    // map takes a function or closure that takes the iteration item type
    // and returns a (potentially different) type.  The tuple variant
    // constructor meets those constraints
    let list_of_statuses: Vec<Status> = (0u32..20).map(f).collect();
}

And it works similarly for full blown tuple types.

fn main() {
    struct Foo(u32);

    // The type and the function have the same name.
    // This is allowed because values and types have distinct namespaces.
    let f: fn(u32) -> Foo = Foo;
    let list_of_statuses: Vec<Foo> = (0u32..20).map(f).collect();
}

There's nothing special about the constructor function in these examples other than the compiler supplying it. You can write your own functions and use them with map or whatever else, too.

I should have probably highlighted specifics of the code I wanted clarity on. My query is specifically about how the following piece of code works

(0u32..20)

Text below is taken from the book.

We have another useful pattern that exploits an implementation detail of tuple structs and tuple-struct enum variants. These types use () as initializer syntax, which looks like a function call. The initializers are actually implemented as functions returning an instance that’s constructed from their arguments.

x..y is the special syntax for constructing a value of type std::ops::Range. There's no tuple involved (to construct a one-element tuple you have to write a comma, like (17,)), those parentheses are just to help the compiler parse the whole expression in the desired way, the same way you'd use them to write something like (1 + 2) * 3.