Is it possible to specialize on a static lifetime?

2019-02-25 05:07发布

I want to specialize &'static str from &'a str. Something like this:

use std::borrow::Cow;

struct MyString {
    inner: Cow<'static, str>,
}

impl From<&'static str> for MyString {
    fn from(x: &'static str) -> Self {
        MyString {
            inner: Cow::Borrowed(x),
        }
    }
}

impl<T: Into<String>> From<T> for MyString {
    fn from(x: T) -> Self {
        MyString {
            inner: Cow::Owned(x.into()),
        }
    }
}

fn main() {
    match MyString::from("foo").inner {
        Cow::Borrowed(..) => (),
        _ => {
            panic!();
        }
    }

    let s = String::from("bar");
    match MyString::from(s.as_ref()).inner {
        Cow::Owned(..) => (),
        _ => {
            panic!();
        }
    }

    match MyString::from(String::from("qux")).inner {
        Cow::Owned(..) => (),
        _ => {
            panic!();
        }
    }
}

The gist is that MyString stores a statically-allocated string literal as a &'static str and all other strings as a String. This allows MyString to avoid having a lifetime parameter—i.e., MyString<'a>, which is critical for my API, all while allowing the caller to pass in any kind of string and have MyString automatically do the correct thing.

The problem is that the code doesn't compile:

error[E0119]: conflicting implementations of trait `std::convert::From<&'static str>` for type `MyString`:
  --> src/main.rs:15:1
   |
7  | impl From<&'static str> for MyString {
   | ------------------------------------ first implementation here
...
15 | impl<T: Into<String>> From<T> for MyString {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyString`

Is there any trick that allows me to do what I want? If not, is lifetime specialization something that Rust will ever support?

标签: rust
2条回答
Evening l夕情丶
2楼-- · 2019-02-25 05:29

Rust 1.25.0 does not have specialization of any kind. If I'm reading the specialization RFC correctly, then lifetime specialization will not be supported even when the RFC is implemented:

A hard constraint in the design of the trait system is that dispatch cannot depend on lifetime information. In particular, we both cannot, and should not allow specialization based on lifetimes:

  • We can't, because when the compiler goes to actually generate code ("trans"), lifetime information has been erased -- so we'd have no idea what specializations would soundly apply.

  • We shouldn't, because lifetime inference is subtle and would often lead to counterintuitive results. For example, you could easily fail to get 'static even if it applies, because inference is choosing the smallest lifetime that matches the other constraints.

(Emphasis mine)

There's some examples further in the link that indicate some of the concrete issues.

I recommend using a Cow to handle the "owned or borrowed" case.

查看更多
啃猪蹄的小仙女
3楼-- · 2019-02-25 05:46

I write this answer after reading this duplicated post asking how to define a method/function that behaves differently when it is passed a static string or a non-static string.

This is not possible, so a workaround may be using a wrapper type to wrap the string argument in an enum:

enum MyString {
    Static(&'static str),
    Heap(String),
}

fn bar(arg: &MyString) {
    match arg {
        &MyString::Static(ref name) => println!("my first pc was {}", name),
        &MyString::Heap(ref name) => println!("I dont know {}", name),
    }
}

fn main() {
    let mut v = Vec::new();

    let forever: &'static str = "zx-spectrum";
    let local: &str = &"commodore64".to_string();

    v.push(MyString::Static(forever));

    // ERROR: try to insert 'a lifetime
    // v.push(Mystring::Static(local));
    v.push(MyString::Heap(local.to_string()));

    v.push(MyString::Heap("muggle".to_string()));

    bar(&v[0]);
    bar(&v[1]);
}

MyString stores a statically-allocated string literal as a &'static str and all other strings as a String.

As pointed in the comments below, the standard library provides a type that fits the borrowed/owned case: the smart pointer Cow.

The enum MyString used in this example is just a specific enum for managing string types.

The only difference stems from a somewhat more specific naming of the enum and its variants related to the specific usage: MyString::Static("forever") versus Cow::Borrowed("forever") and MyString::Heap(str) versus Cow::Owned(str).

Does this help improve mnemonics and code readability? I'm quite sure that this holds only for novices or occasional Rust programmers, not for seasoned Rustaceans.

查看更多
登录 后发表回答