Flexible Input Parameters
There are two generic built-in traits you can use to make your function easy to call.
AsRef (and AsMut)
AsRef allows a function to be called with parameters of differing types - basically anything that allows a reference of the appropriate type to be created cheaply and without ever failing. AsMut is the same but for mutable references.
Example:
fn func1(p1: PathBuf);
fn func2(p1: &Path);
fn func3<S>(p1: S)
where S: AsRef<Path>
{
let p1 = p1.as_ref();
...
}
func1
can only be called with aPathBuf
.func2
can be called with aPath
or a&PathBuf
.func3
can be called with aPath
or a&PathBuf
or anything else that allows aPath
to be borrowed from it - including, handily, strings.
func3
is therefore most flexible and the best approach.
Consider func4
:
fn func4<S>(p1: S, p2: S)
where S: AsRef<Path> { ... }
This works, *but constraints p1
and p2
to be of the same type. You can be
more flexible:
fn func4<N, M>(p1: N, p2: M)
where N: AsRef<Path>,
S: AsRef<Path> { ... }
AsRef<str
>
One very common case is converting things to (static) strings, for example enum values. Use the built-in trait for this, do not define your own:
impl AsRef<str> for XmlDoc {
fn as_ref(&self) -> &str {
match self {
XmlDoc::Unknown => "Unknown",
XmlDoc::None => "None",
XmlDoc::Debug => "Debug",
XmlDoc::Release => "Release",
XmlDoc::Both => "Both",
}
}
}
An AsRef Anti-pattern
AsRef is useful where you only need to borrow the input parameter. If you find yourself doing
let x = param.as_ref().to_owned();
You’re doing it wrong. Use Into instead.
Into
Into has a similar effect to AsRef, in that it makes function APIs more flexible. The difference is that Into is used when you want to take ownership of the parameter. This is often used in constructors, for example:
fn new<S>(model: S) -> Self
where S: Into<String>
{
...
}
You can mix AsRef and Into as required.
- For AsRef, T is the borrowed type (str, Path)
- For Into, T is the owning type (String, PathBuf)
Getting ‘Into’ working for your own types
Do not implement Into
for your types. Implement
From
instead. The standard library has a blanket implementation of Into
which you will get ‘for free’.
If your type has a constructor taking a single parameter of type T, strongly consider
removing the ctor and implementing From<T>
instead. Example follows - note that you can still use
Into
in the implementation for maximum flexibility but you seem to have to use a where
constraint
to get it to compile:
impl<P> From<P> for SolutionDirectory
where P: Into<PathBuf>
{
fn from(sln_directory: P) -> Self {
SolutionDirectory {
directory: sln_directory.into(),
solutions: vec![]
}
}
}
let s: SolutionDirectory = "somepath".into();
Misc Observations
- Functions in std::fs
typically take a
AsRef<Path>
to which you can pass aPathBuf
,Path
,String
,&str
,OsString
andOsStr
among others. Into<String>
will take the usual string types plusBox<str>
,Rc<String>
,Cow<str>
etc.- There is little difference between
&str
andAsRef<str>
.&str
might be more idiomatic - see section ‘Reference Conversions - AsRef’. - The above traits must not fail. Coming soon are TryFrom and TryInto.
Cow for Out Parameters
Cow allows you to put off allocation unless it is absolutely necessary. It often appears in the return position (because it implements DeRef coercion so there is no need for it in the input parameter list).
A few examples. The .into()
call is a call to Cow’s into method -
basically an easy and ergonomic way of creating Cows without having to
specify Cow::Borrowed(data)
or Cow::Owned(data)
everywhere.
use std::borrow::Cow;
/// Returns a Cow::Borrowed with static lifetime
fn func5() -> Cow<'static, str> {
"".into()
}
/// Returns a Cow::Owned with static lifetime
fn func6() -> Cow<'static, str> {
let s = "s".to_owned();
s.into()
}
/// Returns a Cow::Borrowed with lifetime the same as 'data'.
fn func7(data: &str) -> Cow<str> {
Cow::Borrowed(&data[0..1])
}
// Ditto.
fn func8(data: &str) -> Cow<str> {
data[0..1].into()
}
fn main() {
match func8("hello") {
Cow::Borrowed(_) => println!("It's borrowed"),
Cow::Owned(_) => println!("Owned!"),
}
}