Function takes Option<String>

Hey! I need a function that requires an Option<String> and I'd like to be able to pass in a String, &str, or option of either, like:

fn main() {
    let s = String::from("this");
    do_something(s);
    do_something("that");
    do_something(Some(s));
    do_something(Some("then"));
    do_something(None);
}

I can't seem to get it. I thought this would work:

fn do_something<S, T>(s: S) 
where
    S: Into<Option<T>>,
    T: AsRef<str>,
{
    let opt: Option<String> = s.into().map(|s| s.as_ref().to_string());
    println!("{:?}", opt);
}

but it can't seem to infer T when sending an Option.

Thanks.

I think the issue is the ambiguity of this impl in the standard library:
Option as From<T>

Moving the into() function to the caller allows you to add it only when needed.
Updated playground
And even with this change, the final None value needs the turbofish syntax to specify which type of None::<T> it is.

Edit: I originally thought it'd be a confusion on some Option<U> as From<Option<T>> where U: From<T> impl, but I couldn't find that conversion on the docs.

Hey @danjl1100, thanks. Obviously I'm trying to avoid the explicit conversions as I want to use this in a builder pattern and take different inputs seamlessly.

I think, though, if I had to be explicit about anything, it would be the String, not the Option, like:

fn do_something<S>(s: S) 
where
    S: Into<Option<String>>,
{
    let opt: Option<String> = s.into();
    println!("{:?}", opt);
}

fn main() {
    let s = String::from("this");
    do_something(s.clone());
    do_something("that".to_string());
    do_something(Some(s));
    do_something(Some("then".to_string()));
    do_something(None);
}

But I'm still hoping to figure out the way I was originally trying to do it!

Would a simple as_deref still work for you ?

fn foo(_: Option<&str>) {}

fn bar(a: Option<String>, b: Option<&str>) {
    foo(a.as_deref());
    foo(b);
    foo(Some("c"));
}
1 Like

A custom trait with no blanket implementations works for at least the four cases you've presented... except as @danjl1100 stated, when you pass in None, because there's no way for the compiler to know if you meant None::<String> or None::<&str> (and it can't assume it doesn't matter).

For that reason, you might want to consider something like

impl Builder {
    fn maybe_do_something(&mut self, opt: Option<String>) {
        println!("{:?}", opt);
    }    
    pub fn do_something<S: ToString>(&mut self, s: S) {
        self.maybe_do_something(Some(s.to_string()))
    }
    pub fn do_nothing(&mut self) {
        self.maybe_do_something(None)
    }
}
2 Likes

Yeah, the actual use case is a configuration builder. Normally, I would start with a default where optional values are None:

pub struct Config {
    value: Option<String>,
    // ...

and the Builder function would take the string to set the value:

pub value<S: Into<String>>(&mut self, val: S) -> &mut Self {
    self.value = Some(val.into());
    self
}

So the application could call it with a String or &str literal:

cfg.value("something");

That is still the primary use for it, but now I also need to import partial configurations, that come in as options themselves, and it's getting really verbose saying:

if let Some(val) = other_cfg.value() {
    cfg.value(val);
}

instead of:

cfg.value(other_cfg.value());

while also keeping the primary use case.

Plus, only taking a String doesn't give the user a chance to turn off the option by setting it back to None after it's been given a value.

And just making the fields public isn't really an option, because there are further constraints on the accepted values, and/or other transforms that are required.

other_cfg.value() already has a known type, so you could unambiguously pass it to Builder::maybe_do_something. Or, though somewhat of a misuse,

other_cfg.value().map(|s| cfg.do_something(s));

But probably I'd just have an "ingest config" method on the builder that does it all internally, as opposed to trying to make my public API handle it.

self.value_1 = other.value_1.or_else(|| self.value_1);
// ...

If you're set on doing this with a single entry point, and if you're willing to give up all Option cases except Option<String> (or whatever your ultimate value type is), you could support multiple bare values but only Option<String>. Then None will be unambiguous. This will be less ergonomic if someone has ahold of an Option<&str> they're trying to pass in, but for programmers just typing in values, they're going to just pass "literal" and not Some("literal") anyway.

(Or you could support only Option<&str> instead if that matches your use-case better, but that introduces the potential for unnecessary cloning. Though TBH it's probably not at all an actual problem for a builder. [1])


  1. Oh no, my nanoseconds! ↩ī¸Ž

1 Like

I think you can build some abstractions using traits

You can check out this example.

Also you've already moved s on line 12 so you cannot use it on line 14 i guess :thinking:

UPD: Oh, i read the tread. My solution doesn't seem suitable for you so sry.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.