Why does cloning returns reference?

Why does rust-analyzer linting suggest I need to clone this reference twice? Shouldn't one clone be enough?

use polars::prelude::*;

pub fn get_test_fields(cols: &[&SmartString]) -> (Vec<SmartString>, Vec<SmartString>) {
    cols
        .iter()
        .cloned()
        .cloned()
        .partition(|h| h.ends_with("test"))
}

<[T]>::iter() yields references to the elements of the slice, i.e., &T. When the element type T == &SmartString, then the iterator's item type will therefore be &&SmartString.

2 Likes

I guess my underlying question is doesn't clone operate on the underlying memory the reference(s) refer to? And if that's not the case is there anyway in Rust to go from &&&&T to T (where there may be one or more &)?

.cloned() always turns an iterator over &T into an iterator over T. It's defined like that, so there's simply no way to go &&T -> T using a single .cloned().

No plural in sight. It totally does clone the pointed thing, period. There is no magic. Therefore, if you call Clone on a reference-to-reference, you get a copy of the pointed thing, which is a reference itself.


You may be confusing this with auto-deref syntax sugar that allows you to do

let ptr: &&&&T = …;
let value: T = ptr.clone();

but this is purely because the Rust compiler tries very hard to take references or dereference as many times as needed to make the code compile. So the above is really just syntax sugar for

let value: T = (***ptr).clone();

where the compiler figured out how many times to implicitly add *.

This is, of course, completely unrelated to the implementation of Cloned, which again only works for &T -> T by its very definition and signature.

You could of course define an iterator adapter, say DoublyCloned, that goes &&T -> T, and you could implement it by at some point doing a .clone() on the returned element. Then again, type inference and auto-deref would kick in, and the compiler would assign the meaning (*item).clone() to the syntax item.clone(). But that would happen locally, in the implementation of the iterator adaptor itself, and your adaptor would always remove 2 levels of references – you wouldn't be able to use it with iterators that yield anything with less than 2 levels of references.

3 Likes

By the way, Rust works with types, not some baked in concept of memory (or "non-reference memory" or "values you care about memory" or something).

In addition to the auto-deref that is part of method resolution,[1] function parameters can under go deref coercion from &&&&T to &T.

So although Clone::clone also takes &&T to &T,[2] if you annotate a call site properly you can skim off extra layers with deref coercion.

    cols
        .iter()
        .map(|s| String::clone(s))
        .partition(|h| h.ends_with("test"))

Shared references implement Copy, so another alternative is

    cols
        .iter()
        .copied()
        .cloned()
        .partition(|h| h.ends_with("test"))

Personally I find the map clearest, but that may be a matter of taste.


  1. mentioned in the last comment ↩︎

  2. &U to U when T = &U -- working with types, remember ↩︎

1 Like

I can't get this to compile, maybe because the output type needs to be SmartString and not String?

It's a polars thing..

Probably just s/String/SmartString/ everywhere. I only changed it to String because I put it in the playground (which doesn't have polars).

2 Likes

that was it thank you

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.