Passing LazyLock into generic functions (AsRef, Into)

Hi folks,

Recently I played with LazyLock to cache a custom struct with values that can't be const/static and I found that I can't pass LazyLock into a function that uses AsRef<mystruct> instead of simply &mystruct.

(in the example I simply use String instead of a custom struct)

use std::sync::LazyLock;

static CACHED_VALUE: LazyLock<String> = LazyLock::new(|| "This is the cached value".to_owned());

pub fn try_this() {
    let value_not_cached = String::from("abc");
    print_length(value_not_cached); // works ok
    print_length(CACHED_VALUE); // the trait bound LazyLock<std::string::String>: AsRef<str> is not satisfied the trait AsRef<str> is not implemented for LazyLock<std::string::String
}

// stupid example of a function that takes AsRef
pub fn print_length<S>(str: S)
where
    S: AsRef<str>,
{
    let number = str.as_ref().len();
    println!("length is: {number}")
}

This makes sense but I wonder, is there a solution without re-writing functions like print_length that take AsRef<mystruct> or Into<mystruct>.

Thank you.

If you just add * before the lazylock binding you pass into the function it should work I think.

* to dereference into a &String.

print_length(*CACHED_VALUE)

In general, I think it's an anti-pattern to take AsRef or Into arguments in the vast majority of cases. You can just take a reference.

use std::sync::LazyLock;

static CACHED_VALUE: LazyLock<String> = LazyLock::new(|| "This is the cached value".to_owned());

pub fn try_this() {
    let value_not_cached = String::from("abc");
    print_length(&value_not_cached);
    print_length(&CACHED_VALUE);
}

pub fn print_length(str: &str) {
    let number = str.len();
    println!("length is: {number}")
}

Yes, you need an & to call it, but I think that's not a problem. The & shows to the reader that print_length does not take ownership of the argument, so having it here brings value to the reader.

And it works with LazyLock without issue.

4 Likes

I agree with @alice that AsRef is overused for function parameters. However, if you decide to use them anyway, my advice is

  • Dispatch to something non-generic internally anyway to reduce unuseful monomorphization
  • Take something ?Sized by reference if you don't need ownership
pub fn print_length<S>(str: &S)
where
    S: ?Sized + AsRef<str>,
{
    print_length_str(str.as_ref())
}

fn print_length_str(str: &str) {
    let number = str.len();
    println!("length is: {number}")
}

You could submit a PR to add AsRef<T> to LazyLock<T, F> (and LazyCell<T, F>) and see if it's accepted.

1 Like

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.