How to setup default data if nothing is supplied for a Struct

I want to try to have some values in a Struct which will be used as a default if the data isn't supplied. Is this possible in rust? What I really want in the example below is to use a number I supply, or a default value, or zero, etc.

#[derive( PartialEq, Debug, Clone, Default)]
pub struct Thing {
    name: String,
    number: i32,
} 

impl Thing {
    pub fn new( 
        name: &str,
        number: i32,
    ) -> Thing {
        Thing {
        name: name.to_string(),
        number: Default::default(),
        }
    }
}

fn main() 
{
    let mut thing_vector = vec![];
    thing_vector.push(Thing::new("John Doe", 37));
    thing_vector.push(Thing::new("Jill Doe"));
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0061]: this function takes 2 arguments but 1 argument was supplied
  --> src/main.rs:23:23
   |
23 |     thing_vector.push(Thing::new("Jill Doe"));
   |                       ^^^^^^^^^^ ---------- supplied 1 argument
   |                       |
   |                       expected 2 arguments
   |
note: associated function defined here
  --> src/main.rs:8:12
   |
8  |     pub fn new( 
   |            ^^^
9  |         name: &str,
   |         ----------
10 |         number: i32,
   |         -----------

For more information about this error, try `rustc --explain E0061`.
error: could not compile `playground` due to previous error

Rust doesn't have default function arguments. If your type is pure plain-old-data, then omit the constructor, add a (possibly derived) Default impl, and allow users to construct instances using FRU syntax.

Playground with fixed syntax (more idiomatic names and removed Hungarian notation).

Also note that the traditional "overloading" (ad-hoc
polymorhpism) can be emulated in Rust in a more principled manner using traits. For example, in order to overload on arity, you can implement a trait for tuples of different arity. Like this:

impl Thing {
    fn new<T: ThingInit>(init: T) -> Self {
        init.into_thing()
    }
}

pub trait ThingInit {
    fn into_thing(self) -> Thing;
}

impl ThingInit for &str {
    fn into_thing(self) -> Thing {
        self.to_owned().into_thing()
    }
}

impl ThingInit for String {
    fn into_thing(self) -> Thing {
        Thing { name: self, ..Thing::default() }
    }
}

impl ThingInit for i32 {
    fn into_thing(self) -> Thing {
        Thing { number: self, ..Thing::default() }
    }
}

impl ThingInit for (String, i32) {
    fn into_thing(self) -> Thing {
        let (name, number) = self;
        Thing { name, number }
    }
}

impl ThingInit for (&str, i32) {
    fn into_thing(self) -> Thing {
        (self.0.to_owned(), self.1).into_thing()
    }
}
1 Like

What you have in mind is called overloading, which is not possible in Rust.
You can:

  • Have different method names for the two different initializers
  • Use the builder pattern (so, the code becomes: Thing::new("John Doe").with_number(37))

Could you use something like the unwrap_or() to handle the error and insert a default?

Yes, you can.

fn new(name: &str, number: Option<i32>) -> Thing {
     Thing {
             name: name.to_string(),
             number: number.unwrap_or_default(),
      }
}

That worked brilliantly.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.