Собственность, закрытие, FnOnce: много путаницы - PullRequest
2 голосов
/ 25 июня 2019

У меня есть следующий фрагмент кода:

fn f<T: FnOnce() -> u32>(c: T) {
    println!("Hello {}", c());
}

fn main() {
    let mut x = 32;
    let g  = move || {
        x = 33;
        x
    };

    g(); // Error: cannot borrow as mutable. Doubt 1
    f(g); // Instead, this would work. Doubt 2
    println!("{}", x); // 32
}

Сомнение 1

Я не могу запустить свое закрытие ни разу.

Сомнение 2

... но я могу вызывать это закрытие столько раз, сколько захочу, при условии, что я вызываю его через f. Как ни странно, если я это объявляю FnMut, я получаю ту же ошибку, что и в случае сомнения 1.

Сомнение 3

Что означает self в определениях черт Fn, FnMut и FnOnce? Это само закрытие? Или окружение ? Например. из документации:

pub trait FnMut<Args>: FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

Ответы [ 2 ]

2 голосов
/ 25 июня 2019

Некоторые сведения о семействе признаков Fn* необходимы, чтобы понять, как на самом деле работают замыкания.У вас есть следующие черты:

  • FnOnce, которые, как следует из названия, могут быть запущены только один раз.Если мы посмотрим на страницу документов, то увидим, что определение черты практически совпадает с тем, которое вы указали в своем вопросе.Что наиболее важно, это следующее: функция «call» принимает self, что означает, что она потребляет объект, который реализует FnOnce, поэтому, как и любая функция trait, которая принимает self в качестве параметра, она становится владельцемобъекта.
  • FnMut, который допускает мутацию захваченных переменных, или, другими словами, занимает &mut self.Это означает, что когда вы создаете замыкание move || {}, оно будет перемещать любые переменные, на которые вы ссылаетесь, которые находятся за пределами замыкания, в объект замыкания.Объект замыкания имеет тип, который нельзя назвать, то есть он уникален для каждого замыкания.Это вынуждает пользователя выбирать какую-то изменчивую версию замыкания, поэтому &mut impl FnMut() -> () или mut x: impl FnMut() -> ()
  • Fn, которая обычно считается наиболее гибкой.Это позволяет пользователю взять неизменяемую версию объекта, реализующего черту.Сигнатура функции для функции «вызова» этой черты является наиболее простой для понимания из трех, поскольку она принимает только ссылку на замыкание, то есть вам не нужно беспокоиться о владении при передаче или вызове.

Чтобы ответить на ваши индивидуальные сомнения:

  • Сомнение 1: Как видно выше, когда вы move что-то вводите в замыкание, переменная теперь принадлежит замыканию.По сути, то, что генерирует компилятор, похоже на следующий псевдокод:
struct g_Impl {
    x: usize
}
impl FnOnce() -> usize for g_Impl {
    fn call_once(mut self) -> usize {

    }
}
impl FnMut() -> usize for g_Impl {
    fn call_mut(&mut self) -> usize {
        //Here starts your actual code:
        self.x = 33;
        self.x
    }
}
//No impl Fn() -> usize.

И по умолчанию он вызывает реализацию FnMut() -> usize.

  • Сомнение 2: То, что здесь происходит, заключается в том, что замыкания равны Copy, пока каждая из их захваченных переменных равна Copy, то есть генерируемое замыкание будетбыть скопирован в f, так что f в итоге получит Copy этого.Когда вы изменяете определение для f вместо FnMut, вы получаете ошибку, потому что вы сталкиваетесь с подобной ситуацией, чтобы сомневаться 1: вы пытаетесь вызвать функцию, которая получает &mut self, пока вы объявилипараметр должен быть c: T вместо mut c: T или c: &mut T, любой из которых соответствует &mut self в глазах FnMut.
  • Наконец, сомнение 3, параметр self - это само замыкание, которое захватило или переместило некоторые переменные в себя, поэтому теперь оно владеет ими.
2 голосов
/ 25 июня 2019

Здесь вы имеете дело с двумя различными типами замыканий - FnOnce и FnMut.Оба типа замыканий имеют разные соглашения о вызовах.

Если вы определите свое замыкание как

let mut x = 32;
let g  = move || {
    x = 33;
    x
};

, компилятор выведет тип замыкания как FnMut.Хотя замыкание возвращает собственную переменную x, она все равно может вызываться несколько раз, поскольку x равно Copy, поэтому компилятор выбирает FnMut в качестве наиболее общего применимого типа.

При вызовезакрытие FnMut, само закрытие передается по изменяемой ссылке.Это объясняет ваш первый вопрос - прямой вызов g не работает, если вы не сделаете его изменяемым, иначе вы не сможете получить изменяемую ссылку на него.Я также неявно ответил на ваш третий вопрос: self в методах вызова черт Fn относится к самому замыканию, которое можно рассматривать как структуру, содержащую все захваченные переменные.

При вызове f(g), вы передаете FnMut закрытие g как FnOnce закрытие f().Это разрешено, поскольку все FnOnce являются супертрейтом FnMut, поэтому каждое замыкание, реализующее FnMut, также реализует FnOnce.Теперь, когда замыкание преобразовано в FnOnce, оно также вызывается в соответствии с FnOnce соглашением о вызовах:

pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

Закрытие передается значением в этом случае таким образом, вызов потребляет замыкание.Вы можете отдать право собственности на любое ваше значение - оно не должно быть изменчивым, чтобы это работало.

Причина, по которой вы можете вызывать g несколько раз при вызове через f(), заключается в том, что g это Copy.Он захватывает только одно целое число, поэтому его можно копировать столько раз, сколько вы хотите.Каждый вызов f() создает новую копию g, которая используется при вызове внутри f().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...