1

For a project I'm working on (an implementation of an obscure programming language), I have an enum Data, which represents data the interpreter is keeping track of (e.g., a variable). This language has infinite lists/lazy arrays, so I'm using a dyn Iterator for those. Since these lists can be stored in Data, and they themselves contain Data, it's a recursive data structure.

This works fine, until I need to clone some Data, which happens quite often. Currently, Data looks something like this:

pub enum Data {
    Float(f64),
    Int(BigInt),
    String(String),
    Array(Box<dyn Iterator<Item = Data>>)
}

And if I try to #[derive(Clone)] for it:

error[E0277]: the trait bound `dyn Iterator<Item = Data>: Clone` is not satisfied
  --> src/shorispo.rs:28:11
   |
23 | #[derive(Clone)]
   |          ----- in this derive macro expansion
...
28 |     Array(Box<dyn Iterator<Item = Data>>)
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `dyn Iterator<Item = Data>`
   |
   = note: required because of the requirements on the impl of `Clone` for `Box<dyn Iterator<Item = Data>>`
   = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.

I've tried dyn-clone, but this seems to create a sort of chicken-and-egg problem, since Data would need to be Clone.

Is there a way I can do this?

Radvylf Programs
  • 1,429
  • 1
  • 12
  • 31
  • You'll have to `impl Clone for Data` manually, and you'll need something like `CloneIterator` here: https://stackoverflow.com/questions/49594732/how-to-return-a-boxed-clonable-iterator-in-rust – PitaJ Sep 27 '22 at 19:07

2 Answers2

1

You have to implement Clone manually for your type. And to Clone a trait object requires some special sauce, which I derived from this answer.

// We have to provide a special trait for our clonable iterator,
// since Clone requires a Sized type (so we can't call it on a trait object).
trait CloneIterator: Iterator {
    fn clone_box(&self) -> Box<dyn CloneIterator<Item = Self::Item>>;
}

// Implement our special trait for all Cloneable Iterators
impl<T> CloneIterator for T
where
    T: 'static + Iterator + Clone,
{
    fn clone_box(&self) -> Box<dyn CloneIterator<Item = Self::Item>> {
        Box::new(self.clone())
    }
}

// Use our special trait in Data instead
pub enum Data {
    Float(f64),
    Int(i64),
    String(String),
    Array(Box<dyn CloneIterator<Item = Data>>),
}

// Implement Clone manually, making use of `clone_box` for our special trait
impl Clone for Data {
    fn clone(&self) -> Self {
        match self {
            Self::Float(f) => Self::Float(f.clone()),
            Self::Int(i) => Self::Int(i.clone()),
            Self::String(s) => Self::String(s.clone()),
            Self::Array(b) => Self::Array(b.clone_box()),
        }
    }
}

playground

PitaJ
  • 12,969
  • 6
  • 36
  • 55
0

I found a way that works, using dyn-clone:

pub trait CloneIterator: DynClone + Iterator<Item = Data> {}

pub struct Array {
    data: Box<dyn CloneIterator>
}

impl Iterator for Array {
    type Item = Data;

    fn next(&mut self) -> Option<Self::Item> {
        self.data.next()
    }
}

impl Clone for Array {
    fn clone(&self) -> Self {
        Array {
            data: dyn_clone::clone_box(&*self.data)
        }
    }
}

#[derive(Clone)]
pub enum Data {
    Float(f64),
    Int(BigInt),
    String(String),
    Array(Array)
}

By making Array its own struct, I can make it Clone.

Radvylf Programs
  • 1,429
  • 1
  • 12
  • 31