Vec<&str> does not implement AsRef<[AsRef<str>]>? Possible compiler bug?

Hi there - I've been beating my head against this one a little - I've got some fn that takes some type T that implements AsRef<[AsRef]>, and now in some code I want to hold onto one of those in a struct and pass it to that function when the struct is dropped - however, I want to collect it to a Vec and append to it, in the event that something happens (in this case, a coercion from u128 to i64 fails)

use std::time::Instant;
use std::convert::TryInto;

pub trait TagsProvider<S>
where
    Self: AsRef<[S]>,
    S: AsRef<str>,
{
}
impl<S, T> TagsProvider<S> for T
where
    T: AsRef<[S]>,
    S: AsRef<str>,
{
}

fn foo<S: AsRef<str>>(metric: S, ms: i64, tags: impl TagsProvider<S>) {
    println!("{:?}:{:?}", metric.as_ref(), ms);
    tags.as_ref().iter().for_each(|v| println!("{:?}", v.as_ref()))
}

pub struct TimingGuard<S, P>
where
    S: AsRef<str>,
    P: TagsProvider<S>,
{
    metric: S,
    start: Instant,
    tags: P
}

impl<S, P> TimingGuard<S, P>
where
    S: AsRef<str>,
    P: TagsProvider<S>,
{
    pub fn new(metric: S, tags: P) -> Self {
        Self {
            metric,
            start: Instant::now(),
            tags,
        }
    }
}

impl<S, P> Drop for TimingGuard<S, P>
where
    S: AsRef<str>,
    P: TagsProvider<S>,
{
    fn drop(&mut self) {
        let elapsed = self.start.elapsed();
        let Ok(ms): Result<i64, _> = elapsed.as_millis().try_into() else {
            let mut tags: Vec<_> = self.tags.as_ref().iter().map(|t| t.as_ref()).collect();
            tags.push("overflowed");
            foo(self.metric, i64::MAX, tags);
            return;
        };
        foo(self.metric, ms, self.tags);
    }
}

(link to playground: Rust Playground)

The reason I worry this might be a compiler bug is that, if I tear out all the generics in TimingGuard and have it just hold a Vec, this code compiles, but it breaks other previously-compiling code somewhere else in the codebase this is from, which is passing an Vec to foo... which is "spooky action at a distance" enough for me to be concerned

Apologies if I am just missing something obvious, but this has really stumped me!

Hmm that diagnostic leaves a bit to be desired. The problem is that your Vec is a Vec<&str> but self.metric is of type S. So the error is correct.

You can fix the error by calling as_ref on metric as well

foo(self.metric.as_ref(), i64::MAX, tags);

Though you have another unrelated ownership error on the last line as well


You can also modify the signature of foo since you don't actually require both uses of S to be the same type in the body, though there are other ownership errors involved there so you have to pass references instead of values to foo

Playground

use std::convert::TryInto;
use std::time::Instant;

pub trait TagsProvider<S>
where
    Self: AsRef<[S]>,
    S: AsRef<str>,
{
}
impl<S, T> TagsProvider<S> for T
where
    T: AsRef<[S]>,
    S: AsRef<str>,
{
}

fn foo<S: AsRef<str>, T: AsRef<str>>(metric: S, ms: i64, tags: impl TagsProvider<T>) {
    println!("{:?}:{:?}", metric.as_ref(), ms);
    tags.as_ref()
        .iter()
        .for_each(|v| println!("{:?}", v.as_ref()))
}

pub struct TimingGuard<S, P>
where
    S: AsRef<str>,
    P: TagsProvider<S>,
{
    metric: S,
    start: Instant,
    tags: P,
}

impl<S, P> TimingGuard<S, P>
where
    S: AsRef<str>,
    P: TagsProvider<S>,
{
    pub fn new(metric: S, tags: P) -> Self {
        Self {
            metric,
            start: Instant::now(),
            tags,
        }
    }
}

impl<S, P> Drop for TimingGuard<S, P>
where
    S: AsRef<str>,
    P: TagsProvider<S>,
{
    fn drop(&mut self) {
        let elapsed = self.start.elapsed();
        let Ok(ms): Result<i64, _> = elapsed.as_millis().try_into() else {
            let mut tags: Vec<_> = self.tags.as_ref().iter().map(|t| t.as_ref()).collect();
            tags.push("overflowed");
            foo(&self.metric, i64::MAX, tags);
            return;
        };
        foo(&self.metric, ms, &self.tags);
    }
}
1 Like

You are misreading the error message, it's as simple as that. You say that that Vec<&str> doesn't implement AsRef<[AsRef<str>]> but that's not what you signatures ask for.

They ask for AsRef<S> for any type S which user of your types may imagine.

Ad of course Vec<&str> couldn't provide that. What if use S == string? How is that supposed to work?

As an aside:

Possible compiler bug?

If you get a type error that you don't understand, then with overwhelming probability, it's not a compiler bug. (It's possible, but it shouldn't be your first guess.)

4 Likes

YES! thank you, yes, this was exactly it