Best practice: Fallible new vs TryFrom?

Hello all! Really enjoying learning Rust in my free time.

When it comes to these two patterns, what is considered best practice in the Rust community today (Feb 2023)?

  1. Fallible new
// See: https://github.com/greyblake/nutype#how-it-works
impl Username {
  pub fn new(raw_username: impl Into<String>) -> Result<Username, UsernameError> { ... }
}
  1. TryFrom
impl TryFrom<String> for Username {
    type Error = UsernameError;
    
    fn try_from(raw_username: String) -> Result<Self, Self::Error> { ... }
}

Same question raised on the NuType crate here: Best practice in Rust: "fallible new" vs TryFrom? · Issue #18 · greyblake/nutype · GitHub

The answer may well be "both", like how it's common to have an infallible zero-argument new() and implement Default.

6 Likes

I'd also say both. For discoverability in rustdoc.

2 Likes

For me, it depends on the context and semantics.

For something like a username where there are certain requirements on what a valid username looks like, I would normally implement FromStr and (maybe) a Username::parse() function. Then,as part of the Username doc-comments I might add an example showing how something like "Michael-F-Bryan".parse() returns Ok while "$@!#!".parse() returns an Err.

Most of the time I'll try to make new() really trivial and infallible. For constructors that may fail, I find there's almost always a more descriptive name you can use (Identifier::parse(), Config::from_yaml(), etc.).

I generally prefer named fallible constructors or FromStr over TryFrom because "parse a username from a string" tells you a lot more about what it will do and the expected failure modes than "try to convert a string to a username".

6 Likes

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.