Reputation: 819
I'm trying to create a fast, flexible and convenient API that accepts an optional string parameter. I wish the user to be able to pass:
None
"foo"
"foo".to_string()
Some("foo")
(equivalent to "foo"
)Some("foo".to_string())
(equivalent to "foo".to_string()
)As far as I know, the best solution to handle "foo"
or "foo".to_string()
is Into<Cow<'a, str>>
. On the other hand, the best solution to handle "foo"
or Some("foo")
is Into<Option<&'a str>>
.
Thus I tried with this but it doesn't compile:
fn foo<'a, T, O>(_bar: O)
where
T: Into<Cow<'a, str>>,
O: Into<Option<T>>,
foo(Some("aaa"));
error[E0283]: type annotations required: cannot resolve `_: std::convert::Into<std::borrow::Cow<'_, str>>`
--> src/main.rs:12:5
|
12 | foo(Some("aaa"));
| ^^^
|
note: required by `foo`
--> src/main.rs:3:1
|
3 | / fn foo<'a, T, O>(_bar: O)
4 | | where
5 | | T: Into<Cow<'a, str>>,
6 | | O: Into<Option<T>>,
7 | | {}
| |__^
Is it possible to make it work?
Upvotes: 5
Views: 1958
Reputation: 430290
I'm pretty sure you cannot create a function like this and still have it be ergonomically used. The problem is that there can be zero, one, or multiple potential paths through the generic types:
+-----------+
| |
+---------> Option<B> +----------------------+
| | | |
+-+-+ +-----------+ +-----------v----------+
| | | |
| A | | Option<Cow<'a, str>> |
| | | |
+-+-+ +-----------+ +-----------^----------+
| | | |
+---------> Option<C> +----------------------+
| |
+-----------+
That's why you are getting the error you are: It's unclear what the concrete type of T
should be, thus the caller would have to provide it to the compiler. Here I use the turbofish:
foo::<&str, _>(Some("aaa"));
foo::<String, _>(Some("aaa".to_string()));
foo::<&str, Option<&str>>(None);
I'd suggest re-evaluating your API design. Possible directions include:
Creating a custom struct and implementing From
for specific concrete types (e.g. &str
, Option<String>
, etc.). Passing None
will still have the problem because it's unclear what type of None
it is: an Option<&str>
or Option<String>
?
use std::borrow::Cow;
fn foo<'a, C>(_bar: C)
where
C: Into<Config<'a>>,
{
}
struct Config<'a>(Option<Cow<'a, str>>);
impl<'a> From<&'a str> for Config<'a> {
fn from(other: &'a str) -> Config<'a> {
Config(Some(other.into()))
}
}
impl From<String> for Config<'static> {
fn from(other: String) -> Config<'static> {
Config(Some(other.into()))
}
}
impl<'a> From<Option<&'a str>> for Config<'a> {
fn from(other: Option<&'a str>) -> Config<'a> {
Config(other.map(Into::into))
}
}
impl From<Option<String>> for Config<'static> {
fn from(other: Option<String>) -> Config<'static> {
Config(other.map(Into::into))
}
}
fn main() {
foo("aaa");
foo("aaa".to_string());
foo(Some("aaa"));
foo(Some("aaa".to_string()));
foo(None::<&str>);
}
Switch to a builder pattern — my preferred direction:
use std::borrow::Cow;
#[derive(Debug, Clone, Default)]
struct Foo<'a> {
name: Option<Cow<'a, str>>,
}
impl<'a> Foo<'a> {
fn new() -> Self {
Self::default()
}
fn name<S>(mut self, name: S) -> Self
where
S: Into<Cow<'a, str>>,
{
self.name = Some(name.into());
self
}
fn go(self) {
println!("The name is {:?}", self.name)
}
}
fn main() {
Foo::new().go();
Foo::new().name("aaa").go();
Foo::new().name("aaa".to_string()).go();
}
Note that this removes the need for the caller to specify Some
at all; using the name
function implies presence. You could make a without_name
function to set it back to None
if needed.
See also:
Upvotes: 8