Cannot find method on a type

pub fn iterate_over_dir<P: AsRef<Path>>(dir: P) {
    if dir.parent().is_some() {
}
}

If I have the arg dir as a Path, this code work. If I have the dir in its presented form above, compiler tells me that there is no parent method on arg of type P. Surely the P is something that can be references as Path, which in turn will have the parent method?

The only think you know about the generics you get is that they satisfy your trait bounds, so the only thing you can do with P is call as_ref() on it to get a &Path. But once you do, you'll have a &Path.

pub fn iterate_over_dir<P: AsRef<Path>>(dir: P) {
    if dir.as_ref().parent().is_some() {
        // ^^^^^^^^^
    }
}
2 Likes

Hehe, thanks. Not obvious and tbh, somewhat counter intuitive, as normally one would expect that you have methods on a object irrelevant of the fact if you are accessing it via ref or by val.
But thanks, it makes perfect sense in the light you've put it on.
Best regards

But that's not the problem here. It's not value vs. reference, it's Path vs some unknown type T. The as_ref() method performs a type conversion; there's nothing counter-intuitive about this.

2 Likes

Yes it is. For me.
AsRef shouldn't be explicitly called if the trait says that the type P can be uses as a ref to a Path.
My view on that is firmed from such position that you don't have to explicitly dereference objects behind references, and yet you can access methods that are implemented for them;


struct a {
}
impl a {
    fn call_me(&self) {
        println!("called");
    }
}

fn main()  {
    let a = a {};
    let a_ref = &a;
    a_ref.call_me();//works even though we are accessing the a object via ref.
}

There is a trait that implicitly makes a type act like a reference: std::ops::Deref, which the compiler invokes automatically when needed, and which is almost never explicitly called.

AsRef is not Deref; AsRef is just a trait with one method with a particular signature. If you want to call that method you have to do it explicitly; the compiler doesn't do it for you.

4 Likes

If I can call method on object I should (intuitively) be able to call the same methods via ref to that object.
I understand what you are saying. All I am saying is that the current behavior of rust is not intuitive.

Alas, it should. It's not a built-in, and the compiler has no idea it is meant to perform a type conversion. What if there were a trait for launching nukes? Should that also be called by the compiler at arbitrary points? That's not how things work, for the better.

I'm aware. But there isn't any intermediate trait or type conversion involved there. If you have a T, you can get a &T or a &mut T it without knowing about any other traits or methods; the compiler just has to grab the address of the value, which is a pointer to the same type. There are no generics involved in that whatsoever, just the very fundamental and trivial idea of indirection.

This is substantially different from where arbitrary code execution may be involved. All the compiler knows about a generic type parameter is that it has some set of methods you are allowed to call on them. It's not obvious at all that if/when/why/how these methods should be called, and so they shouldn't be called automatically. Methods aren't only for type conversion; they have a myriad other purposes, and as such, it would be wrong to just assume that they are all for type conversion and should always be called automatically.

Neither is in my original example. The only thing that was happening is was getting the ref to the object. Seriously. I don't want to have discussion if this is good or bad design. All I'm saying is that this is unintuitive.

What about Deref trait? What then, would launching nukes be ok when compiler does it when Deref trait is involved but not AsRef?
Seriously... It is not intuitive.
Especially in view of the fact that there isn't any other method to actually use from that trait. User in order to access the object HAS TO manually call the only available method... If that is intuitive....

Actually all the compiler does in that case is to perform ref to ref conversion. That's it.

The only trait that has this kind of implicit behavior is Deref, which is a special built-in trait (lang item). Note that, if you replace AsRef<Path> with Deref<Target=Path> the original code does work; although this limits the types you can pass into the function.

AsRef is not a lang item, so the compiler cannot apply any special magic to it. The Rust community generally regards the explicitness here as a positive thing, as avoiding special magic in general makes the language more consistent and easier to understand in the long run.

2 Likes

Moderator note: Please don't tell people that they're wrong about what they consider intuitive.

Something intuitive for one person may not be intuitive for another; keep in mind that each individual has their own unique perspective.

3 Likes

I think your frustration comes from not understanding the difference between AsRef and Deref, or perhaps confusing what they are supposed to do.

3 Likes

I understand the difference. My frustration stems from the fact that the behavior is non orthogonal when we are using &obj and object that is behind AsRef.

If the function signature looked like this instead, which conversion method should the compiler pick, as_ref() or borrow()? There’s no guarantee that they’re equivalent.

pub fn iterate_over_dir<P: AsRef<Path> + Borrow<Path>>(dir: P) { … }
1 Like

I think you can make that point even clearer with this signature:

fn foo<T>(value: T)
where
    T: AsRef<Path>,
    T: AsRef<OsStr>,
    T: AsRef<[u8]>,
{}

Which is kind of a good jumping point to why AsRef is a generic trait but Deref isn't.

2 Likes

That is actually very good example.
So in such case, how would I refer to the as ref that I need?
<Type as Trait>::as_ref
Would that be the correct way?

The style I would use in that case would be to simply help inference like:

let bytes: &[u8] = value.as_ref();

Thanks, I appreciate it. But surely if you put explicit type, the as_ref shouldn't be required as you obviously know which type you want?
Like I've said, I don't want to start discussion, is it bad or good. To me it is simply not-intuitive. Full stop.