I want to implement a trait that allows assigning generic types. So far I have tested this for u32
and String
types:
trait Test {
fn test(&self, input: &str) -> Self;
}
impl Test for String {
fn test(&self, input: &str) -> Self {
input.parse().unwrap()
}
}
impl Test for u32 {
fn test(&self, input: &str) -> Self {
input.parse().unwrap()
}
}
fn main() {
let mut var = 0u32;
let mut st = String::default();
var = var.test("12345678");
st = st.test("Text");
println!("{}, {}", var, st);
}
I know this code is not perfect, and I should be using a Result
return instead of unwrapping, but please set this aside as this is a quick example. The implementations for u32
and String
are exactly the same, so I would like to use a default implementation for both instead of copying & pasting the code. I have tried using one, but as the returned type Self
differs in both, compiler cannot determine the type size and errors.
How could I write a default implementation in this case?
The following bounds on Self
are required for the default implementation:
Self: Sized
because Self
is returned from the function and will be placed in the caller's stackSelf: FromStr
because you're calling parse()
on input
and expecting it to produce a value of type Self
<Self as FromStr>::Err: Debug
because when you unwrap
a potential error and the program panics Rust wants to be able to print the error message, which requires the error type to implement Debug
Full implementation:
use std::fmt::Debug;
use std::str::FromStr;
trait Test {
fn test(&self, input: &str) -> Self
where
Self: Sized + FromStr,
<Self as FromStr>::Err: Debug,
{
input.parse().unwrap()
}
}
impl Test for String {}
impl Test for u32 {}
fn main() {
let mut var = 0u32;
let mut st = String::default();
var = var.test("12345678");
st = st.test("Text");
println!("{}, {}", var, st);
}
A generic blanket implementation is also possible, where you automatically provide an implementation of Test
for all types which satisfy the trait bounds:
use std::fmt::Debug;
use std::str::FromStr;
trait Test {
fn test(&self, input: &str) -> Self;
}
impl<T> Test for T
where
T: Sized + FromStr,
<T as FromStr>::Err: Debug,
{
fn test(&self, input: &str) -> Self {
input.parse().unwrap()
}
}
fn main() {
let mut var = 0u32;
let mut st = String::default();
var = var.test("12345678");
st = st.test("Text");
println!("{}, {}", var, st);
}
This implementation, similar to the default implementation, allows you to pick which types get the implementation, but it's also similar to the generic implementation, in that it doesn't require you to modify the trait method signature with any additional trait bounds:
trait Test {
fn test(&self, input: &str) -> Self;
}
macro_rules! impl_Test_for {
($t:ty) => {
impl Test for $t {
fn test(&self, input: &str) -> Self {
input.parse().unwrap()
}
}
}
}
impl_Test_for!(u32);
impl_Test_for!(String);
fn main() {
let mut var = 0u32;
let mut st = String::default();
var = var.test("12345678");
st = st.test("Text");
println!("{}, {}", var, st);
}
The key differences between the 3 approaches:
Test
must be sized, and have a FromStr
impl with a debuggable error type.Test
implementations.