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

5

11

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],
        }
    }
}
Seavey answered 25/11, 2020 at 20:7 Comment(1)
" Isn't a None of type T the same as any other None?" - no, None is a variant of Option<T>. Its full name would be Option::<T>::None, but you often don't need that since the type is usually inferred.Launalaunce
R
8

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.

Reiss answered 25/11, 2020 at 21:56 Comment(0)
C
6

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.

Caricature answered 25/11, 2020 at 21:51 Comment(1)
Added note: You can just use repeat_with(|| None), it being an Option<T> is inferred. Just for clarity here.Caricature
P
3

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.

Presentable answered 25/11, 2020 at 20:11 Comment(0)
P
2

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.

Parks answered 25/11, 2020 at 20:14 Comment(5)
Your linked RFC doesn't have any bearing to the question at hand: Variant types may not have inherent impls, or implemented traits.Transvalue
@IbraheemAhmed If I understand you correctly there is no type theory reason that a None cannot be cloned, but rather the static analyzer doesn't have that functionality yet. Correct?Seavey
@Seavey Well, the type theory reason is that enum variants are in fact not types. Instead, they are variants (subtypes) of an enum and their behaviour is determined by the enum that they are contained in. The way that the Rust compiler views enum variants may or may not change in the future.Parks
@Seavey I updated my question to make that more clear.Parks
@Transvalue Updated.Parks
H
0

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])
Haubergeon answered 25/8, 2023 at 13:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.