I have made the following newtype:
/// Create the Name newtype
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
#[sqlx(transparent)]
pub struct Name(String);
/// Implement the Name type
impl Name {
pub fn new(name: &str) -> Result<Self, Vec<&'static str>> {
let mut errors = Vec::new();
// Name must be between 3 and 50 characters
if !validate_length(name, Some(3), Some(50), None) {
errors.push("Name must be between 3 and 50 characters");
}
// Name must start with a letter
if !name.chars().next().map_or(false, |c| c.is_alphabetic()) {
errors.push("Name must start with a letter");
}
// All Name rules comply, return the Name
if errors.is_empty() {
Ok(Self(name.to_string()))
} else {
Err(errors)
}
}
}
// Convert String to a Name type
impl From<String> for Name {
fn from(value: String) -> Self {
Self(value)
}
}
// Implement the Display trait for Name
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
Now, I need to convert a var that is being set using an env to convert to the Name type. I have no clue how to do this:
// admin user
let admin_user_name= env::var("ADMIN_USER_NAME")
.unwrap_or_else(|_| "admin".to_string());
So, admin_user_name must be of type Name.
Since you have implemented the From<String>
trait, you can declare the type of the variable and use .into()
. The compiler will know it has to convert from String
to Name
:
let admin_user_name: Name = env::var("ADMIN_USER_NAME")
.unwrap_or("admin".to_string())
.into();
or you can use an explicit Name::from()
:
let admin_user_name = Name::from(env::var("ADMIN_USER_NAME")
.unwrap_or("admin".to_string()));
If you need to use it in a place the compiler knows to be Name
, the into()
is the easiest way to do it. For example, as a function argument of type Name
.
PS: Note that the unwrap_or
I used is evaluated every time, even if env::var
returned Ok
. The method you used, unwrap_or_else
only call the function if env::var
is not Ok
, which can be desirable if the function does more than directly return a String (e.g. if there are side-effects or if it's heavier to compute).
1 Like
The expression above gives you a String
, so this is the type of admin_user_name
. I assume you want to call Name::new
. But your Name::new(name: &str)
function takes a parameter of type &str
.
You can coerce a String
to &str
by prefixing the expression with &
or by using two variables:
let admin_user_string = env::var("ADMIN_USER_NAME")
.unwrap_or_else(|_| "admin".to_string());
let admin_user_name = Name::new(&admin_user_string);
Note that this will create an intermediate String, which is then converted to another new String, which wastes an allocation. To avoid that you may want to change Name::new(name: &str)
to Name::new(name: String)
.
2 Likes
Another thing worth mentioning is that your implementation of From<String>
isn't calling the new
method, which means that it would create a Name
that hasn't gone through the validation that you created for it.
3 Likes
There were a few good remarks here, so I made a 2nd version.
The problem with your initial code is that, as @firebits.io said, the From
implementation doesn't use your validation code. And since From
doesn't allow for errors, you should use TryFrom
instead. Also, as @jumpnbrownweasel noticed, it's more appropriate here to use a String
instead of an &str
in new()
.
I declared a struct for the error, to avoid repetitions and to make changes easier. The TryForm
then looks like this (the whole code is in the playground at the bottom):
#[derive(Debug)]
pub struct ErrorName(Vec<&'static str>);
// Convert String to a Name type
impl TryFrom<String> for Name {
type Error = ErrorName;
fn try_from(value: String) -> Result<Self, Self::Error> {
Name::new(value)
}
}
You can then manage any error with this pattern (in the playground, I put another form that gives default values in case of errors, but the one below is better IMO):
// manages the possible error:
let admin_user_name = Name::try_from(env::var("ADMIN_USER_NAME").unwrap_or("admin".to_string()));
match admin_user_name {
Ok(name) => { println!("admin_user_name = {name}"); }
Err(e) => { println!("## ERROR: {}", e.0.join(", ")); }
}
(I removed some of the non-std derives of Name
in the playground)