HiDefender
HiDefender

Reputation: 2368

Why can't None be cloned for a generic Option<T> when T doesn't implement Clone?

Given a struct with a generic Option<T> where T might not implement Clone why can't None be cloned? Isn't a None of type T the same as any other None? For example:

struct Foo<T> {
    bar: Vec<Option<T>>,
}

impl <T> Foo<T> {
    fn blank(size: usize) -> Foo<T> {
        Foo {
            bar: vec![None; size],
        }
    }
}

Upvotes: 11

Views: 2167

Answers (5)

Chayim Friedman
Chayim Friedman

Reputation: 70900

If the number of items is not too large, so that it overflows the stack if placed on it, you can construct the Vec from an array. The advantage of arrays is that they can be constructed from const values, even if the type is not Copy:

const NONE: Option<NonClone> = None;
Vec::from([NONE; 10])

Upvotes: 0

user4815162342
user4815162342

Reputation: 154876

Isn't a None of type T the same as any other None?

Definitely not! Unlike reference-based languages where null is typically internally represented as a null pointer (pointer whose address is 0), Rust's Option<T> introduces no indirection and stores T inline when the option is Some. Since all enum variants have the type of the enum and therefore the same size, the None variant must still occupy at least as much space as T.

Having said that, it is technically true that the None value could be cloned without T being Clone simply because the None variant of the enum doesn't contain the T, it only stores the discriminator and reserves space that could contain T if the variant were to change to Some. But since Rust enum variants are not separate types, a trait bound defined for the enum must cover all variants.

See other answers more detailed explanations and instructions how to create a vector of None values of a non-cloneable Option.

Upvotes: 8

Ibraheem Ahmed
Ibraheem Ahmed

Reputation: 13518

An Option can only be cloned if the inner T implements Clone:

impl<T> Clone for Option<T>
where
    T: Clone, 

Isn't a None of type T the same as any other None?

Actually, no. The Rust compiler does not even view None as a type. Instead, None is just a variant (subtype) of Option<T>. Try comparing Nones of two different Ts:

let a: Option<String> = None;
let b: Option<u8> = None;

assert_eq!(a, b)

You will see that they are in fact completely unrelated types:

error[E0308]: mismatched types
 --> src/main.rs:4:5
  |
4 |     assert_eq!(a, b)
  |     ^^^^^^^^^^^^^^^^ expected struct `String`, found `u8`
  |
  = note: expected enum `Option<String>`
             found enum `Option<u8>`

So the Rust compiler actually sees None as Option::<T>::None. This means that if T is not Clone, then Option<T> is not Clone, and therefore Option::<T>::None cannot be Clone.

To make your code compile, you must constrain T to Clone:

struct Foo<T: Clone> {
    bar: Vec<Option<T>>,
}

impl <T: Clone> Foo<T> {
    fn blank(size: usize) -> Foo<T> {
        Foo {
            bar: vec![None; size],
        }
    }
}

Now the compiler knows that T is Clone, and the implementation of Clone for Option<T> (and Option::<T>::None) is fulfilled.

Upvotes: 2

user2722968
user2722968

Reputation: 16475

As the other answers correctly point ouf, this is due to the way the vec!-macro is implemented. You can manually create a Vec of any Option<T> without requiring T to be Clone:

let bar = std::iter::repeat_with(|| Option::<T>::None).take(size).collect::<Vec<_>>();

This will create size-number of Option::<T>::None and place them in a Vec, which will be pre-allocated to the appropriate size. This works for any T.

Upvotes: 6

Aplet123
Aplet123

Reputation: 35512

If one part can't be cloned, then no parts can be cloned. Think about this from a type perspective: you have a variable called foo of type Option<T>, where T isn't clonable. Sure, None can be cloned, but Some(_) can't, and the type of the variable doesn't tell you which it is. So, from a compile-time perspective, you can't clone the option.

Upvotes: 3

Related Questions