一个函数能接受类型为Option、&str或String的参数吗?

5
我正在尝试创建一个快速、灵活和方便的API,该API接受一个可选的字符串参数。我希望用户能够传递以下内容:
- None - "foo" - "foo".to_string() - Some("foo")(相当于"foo") - Some("foo".to_string())(相当于"foo".to_string())
据我所知,处理"foo"或"foo".to_string()的最佳解决方案是Into>。另一方面,处理"foo"或Some("foo")的最佳解决方案是Into>。
因此,我尝试了以下代码,但它无法编译:
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  | | {}
   | |__^

游乐场

能让它工作吗?


1
使用trait +泛型。我非常确定这是一个重复的内容。 - Boiethios
1
我并不100%确定函数签名是否有效。即使您为O传递了某些具体类型,也不一定存在对应的唯一T,对吗? - Peter Hall
1
@synek317 模拟函数重载:https://dev59.com/IF8e5IYBdhLWcg3w4to_#25265605 - Boiethios
2
可能是如何近似方法重载?的重复问题。 - Boiethios
显示剩余4条评论
1个回答

7

我很确定您不能创建这样的函数并使其符合人体工学使用。问题在于可能会出现零个、一个或多个潜在的通用类型路径:

            +-----------+
            |           |
  +---------> Option<B> +----------------------+
  |         |           |                      |
+-+-+       +-----------+          +-----------v----------+
|   |                              |                      |
| A |                              | Option<Cow<'a, str>> |
|   |                              |                      |
+-+-+       +-----------+          +-----------^----------+
  |         |           |                      |
  +---------> Option<C> +----------------------+
            |           |
            +-----------+

这就是你遇到错误的原因:不清楚T的具体类型是什么,因此调用者需要将其提供给编译器。这里使用turbofish来指定类型:
foo::<&str, _>(Some("aaa"));
foo::<String, _>(Some("aaa".to_string()));
foo::<&str, Option<&str>>(None);

我建议重新评估您的API设计。可能的方向包括:

  1. 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>);
    }
    
  2. 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.

另请参阅:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接