Creating owned type from references

In the below code I was trying to create owned type m from a reference l . but that line gives me error. what Am I doing wrong here ? How do I create a type Foo from &Foo here


struct Foo{}

fn main() {    
    let k = Foo{};
    let l = &k;
    let m = *l; // this line give me error
}
error:

--> src/main.rs:6:13
  |
6 |     let m = *l;
  |             ^^
  |             |
  |             move occurs because `*l` has type `Foo`, which does not implement the `Copy` trait
  |             help: consider borrowing here: `&*l`

By default, Rust types aren't copiable. This means that when accessed by-value, they are moved. But you are not allowed to move out of the referent of a reference, because the reference would then point to invalidated memory.

There are two possible solutions:

  • make the type Copy (which implies Clone), which is possible only if it consist only of Copy types, transitively (of course it works for an empty struct too)
  • or only make it Clone, and explicitly .clone() it when you have a reference.
3 Likes

To elaborate on @H2CO3's response, sometimes it's useful to ask what you really mean when you say "I want to get an owned type from this reference" and how that affects the data inside your type.

Some types are trivially copyable and getting an owned value is just a case of copying all the bytes from behind your reference into a new variable. A good example of this are things like u32 and std::net::Ipv6Addr (an IPv6 address is just a [u8; 16] under the hood). These types are normally treated as plain values and one instance is semantically and logically indistinguishable from another (e.g. in let x = 42_u32; let y = 42_u32, there is no real difference between x and y). We use the Copy trait here, and if Foo was Copy then it would be fine to do let m = *l to get an owned version.

Other types are still treated like values, but they aren't trivially copyable (typically because they involve heap allocations) so we need to use phrase things differently. If you look at something like String or Vec<f32>, going from a reference (i.e. &String) to an owned value would mean creating a completely new String populated with a copy of all the same characters. For this, we use the Clone trait (i.e. some_string_reference.clone()) to create a deep copy where we recursively clone everything inside a variable.

There are also variables where different instances aren't really interchangeable (one might say "non-fungible") because each "object" has its own identity and state, and it doesn't make sense to compare them or make deep copies. A good example of this is a std::net::TcpStream or a reqwest::Client. Here, the only way we can go from a borrowed value to an owned one is if both our variables end up still pointing at the same object. You see this a lot in garbage collected languages in the form of let x = new Foo(); let y = x because shared references are the default for everything except primitives. The way you get this in Rust is through some sort of smart pointer which allows shared ownership of a value, and the clone() method will create a new "handle" to the same object (e.g. let x: &Arc<TcpStream> = ...; let y = x.clone()).

The result is often still the same - make sure your type implements Clone and call its clone() method - but the semantics are different, especially when we are talking about modifying a cloned value.

5 Likes

We can use core::ptr::read function to copy a value from a pointer. We’ve got a reference, not a pointer, so this operation would be safe.

struct Foo;

fn main() {
    let k = Foo;
    let ref l = k;
    let m = unsafe {core::ptr::read(l)};
}

This is a solution for situations in which you cannot modify your Foo; otherwise, you’d better derive(Clone).

This is essentially UB if the value cannot be Copy (e.g. if it owns an allocation, this will lead to double-free).

3 Likes

As @Cerber-Ursi pointed out, this can backfire badly if the value could not be made to be Copy, e.g. due to a future change. Making a type Copy gives you exactly the guarantee which you need to make core::ptr::read(l) sound.

So I don't really see many use-cases where core::ptr::read(l) is a good idea, especially never for beginners as it's really dangerous to do. The only case I could imagine is a 3rd party struct which is !Copy but guaranteed to be copyable. I have never seen such API, has anyone else?

1 Like

Here some variants of what you can (safely) do:

#[derive(Clone)]
struct Foo {}

#[derive(Clone, Copy)]
struct Bar {}

struct Point {
    a: i32,
    b: i32,
}

struct PointWithName {
    point: Point,
    name: String,
}

fn main() {
    // `Foo` can be cloned but is not `Copy`:
    let k = Foo {};
    let l = &k;
    let _m = l.clone(); // we must use `.clone()`
    
    // `Bar` is `Copy`:
    let a = Bar {};
    let b = &a;
    let _c = *b; // this effectively copies the value
    let _c = b.clone(); // or we can clone, it's the same here

    // `PointWithName` has no `Clone` implementation:
    let n = PointWithName {
        point: Point { a: 1, b: 2 },
        name: "P".to_string(),
    };
    let o = &n;
    // But we can copy/clone the elements and construct a new struct:
    let _p = PointWithName {
        point: Point { a: o.point.a, b: o.point.b },
        name: o.name.clone(),
    };
}

(Playground)

1 Like

Another common solution is to take the existing owned object from behind the reference while leaving behind something else so that the reference remains valid. Some common ways to do this are:

  • Option::take(&mut self)->Self, which leaves None behind
  • std::mem::take<T:Default>(&mut T)->T, which leaves T::default() behind
  • std::mem::replace<T>(&mut T, other:T)->T, which leaves other behind
6 Likes

I don't really think that a ZST can own any allocations :face_with_monocle:

That was an example. Point is, if type can't soundly be Copy (for any reason), then it can't soundly be ptr::read without forgetting the original (and sometimes even with it, as with self-referential types).

1 Like

ZSTs can still have Drop impl. It's practical to make drop guards in Rust as they're only way to run cleanup code on unusual control flows like panic unwinding and async cancelation. If those cleanup code only needs to access some global resources like thread locals it doesn't need any fields on it, which makes them ZST.

3 Likes

Even without Drop, materializing a token out of thin air can break otherwise safe APIs.

1 Like

@trentj, @Hyeonu, @Cerber-Ursi, ok. I understood what you mean. Anyway I still don’t understand what are the problems core::ptr::read can cause in this case :face_with_monocle:

In this case there're none, except that you can derive(Copy) on Foo and replace ptr::read with dereference.

3 Likes

In this case, since the only reason you know ptr::read is OK is because you know the type is Foo, you could also simply create a new Foo:

//  let m = unsafe { std::ptr::read(l) };
    let m = Foo;

unsafe here is totally unnecessary.

But if Foo isn't your type, and doesn't give you a way to create a new one (like Copy, Clone or a public constructor), then you also can't assume ptr::reading it is sound simply because it is a ZST.

3 Likes

The problem is that if you later modify Foo, you will run into problems and the compiler won't warn you about it:

struct Foo {
    _s: String,
}

fn main() {
    let k = Foo { _s: "Hello".to_string() };
    let ref l = k;
    let _m = unsafe {core::ptr::read(l)};
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.72s
     Running `target/debug/playground`
free(): double free detected in tcache 2
timeout: the monitored command dumped core
/playground/tools/entrypoint.sh: line 11:     8 Aborted                 timeout --signal=KILL ${timeout} "$@"

To avoid this problem, you can do the following:

#[derive(Clone, Copy)] // SAFETY: `Foo` must be able to implement `Copy` due to `core::ptr::read(l)` in `main`
struct Foo {
    _s: String,
}

This then would give you a compiler error:

   Compiling playground v0.0.1 (/playground)
error[E0204]: the trait `Copy` may not be implemented for this type
 --> src/main.rs:1:17
  |
1 | #[derive(Clone, Copy)]
  |                 ^^^^
2 | struct Foo {
3 |     _s: String,
  |     ---------- this field does not implement `Copy`
  |
  = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0204`.
error: could not compile `playground` due to previous error

(Playground)

But if you add Copy in the original example …

#[derive(Clone, Copy)]
struct Foo;

… then you don't need core::ptr::read(l) at all:

fn main() {
    let k = Foo;
    let ref l = k;
    let _m = *l;
}

(Playground)

So using core::ptr::read(l) is pretty much useless here. If it's safe sound to use and if you thus could add #[derive(Copy)] then you don't wouldn't need it. If you don't add #[derive(Copy)], then you might later cause undefined behavior at runtime because you make some changes without noticing that these cause core::ptr::read(l) to result in undefined behavior.

2 Likes