Почему это превращается в переполнение стека? - PullRequest
3 голосов
/ 07 февраля 2020

Простой тестовый случай, который завершается неудачно с переполнением стека:

// trait to say FnMut has a clone_box method
pub trait ClonableFnMut<A>: FnMut(A) {
    fn clone_box(&self) -> Box<dyn ClonableFnMut<A> + Send + 'static>;
}

// overridden .clone() for Box<ClonableFnMut> that calls .clone_box on f
impl<A: 'static> Clone for Box<dyn ClonableFnMut<A> + Send + 'static> {
    fn clone(&self) -> Self {
        self.clone_box()
    }
}

// .clone_box() on FnMut clones itself and wraps itself in a new Box
impl<A, F: FnMut(A) + Clone + Send + 'static> ClonableFnMut<A> for F {
    fn clone_box(&self) -> Box<dyn ClonableFnMut<A> + Send + 'static> {
        Box::new(self.clone())
    }
}

fn main() {
    let mut f: Box<dyn ClonableFnMut<u8> + Send + 'static> = Box::new(|_x|{});

    println!("{:?}", f(3));
    println!("{:?}", f.clone()(4));
}

Теоретически:

  1. Вызов .clone() на Box<ClonableFnMut>.
  2. Вызовы пользовательской реализации .clone_box() на внутреннем FnMut.
  3. Внутренний FnMut теперь может вызывать .clone() для себя, так как он помечен как Клон.
  4. .clone_box() возвращает этот клонированный FnMut (себя) в новом ящике

Но это на самом деле:

  1. звонки .clone() вручную Box<ClonableFnMut>.
  2. звонки .clone_box() в внутренний Box<FnMut>.
  3. вызывает self.clone(), что означает self = box.
  4. custom Box<FnMut> clone() вызывается снова, начинается с шага 1.

Какова действительная причина для шага 4?

1 Ответ

3 голосов
/ 08 февраля 2020

То, что происходит на самом деле, немного отличается:

  1. Кто-то звонит clone на Box ...
  2. ... что вызывает clone_box на тот же Box ...
  3. ... который вызывает clone на тот же Box снова, закрывая l oop.

Это происходит потому, что ваша реализация Clone вызывает только self.clone_box(), но clone_box является методом черты ClonableFnMut. Эта черта реализована, в частности, на Box<dyn ClonableFnMut<A> + Send + 'static> из-за полной реализации, требования которой F: FnMut(A) + Clone + Send + 'static удовлетворяет сама коробка.

Чтобы избежать этого, вам нужно принудительно Clone реализация для вызова метода clone_box для содержимого Box, а не для самого Box. Есть два очевидных способа в немного разных стилях:

  1. Заменить self.clone_box() на self.deref().clone_box() и добавить требуемый импорт use std::ops::Deref; куда-нибудь.

  2. В качестве альтернативы замените self.clone_box() на (**self).clone_box(), что не требует дополнительного импорта, но выглядит слегка загадочно c. Обратите внимание, что аргумент clone, &self, в основном является синтаксисом c sugar для self: &Self, поэтому первый * разыменовывает его с &Box<F> до Box<F>, а второй разыменяет его еще раз на F. Вызов clone_box затем автоматически ссылается на &F, так как для этого тоже требуется &self.

...