首页 > 解决方案 > 不能在一个函数调用中借用 self 两次

问题描述

我是一个新手rust,对函数调用感到震惊。很抱歉,我无法制作干净的错误示例,因为iced::PickList.

问题是——只要我在该调用中只使用一种方法——构建过程和程序就可以工作,但是如果我只是将第二个需要的方法的签名更改为不是静态的——我根本无法构建和修复 bowwor 错误.

操场上的完整工作(构建和启动)示例。

虽然看起来像这样,但通过Self::port_names()调用,它可以工作。

extern crate iced;
extern crate midir;

use std::cell::RefCell;
use std::error::Error;
use std::io::{stdin, stdout, Write};
use std::option::Option::Some;
use std::sync::mpsc::{channel, Sender};

use iced::{
    button, executor, pick_list, scrollable, Align, Application, Button, Column, Command,
    Container, Element, Length, PickList, Scrollable, Settings, Space, Text,
};
use midir::{Ignore, MidiInput, MidiInputConnection, MidiInputPort, MidiInputPorts};

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
struct Port {
    pub index: usize,
}

struct BigNote {
    note: i32,
    need_init: bool,
    client_name: String,
    scroll: scrollable::State,
    ports_list: pick_list::State<Port>,
    selected_port: Port,

    connection: Option<MidiInputConnection<Sender<Vec<u8>>>>,
}

impl BigNote {
    fn midi_in(&self) -> MidiInput {
        MidiInput::new(&self.client_name.to_string()).unwrap()
    }
    fn ports_names() -> Vec<Port> {  // this is static method
        let mut names: Vec<Port> = Vec::new();
        names.push(Port { index: 0 });
        names.push(Port { index: 1 });
        names.into()
    }
}

impl Application for BigNote {
    type Executor = executor::Default;
    type Message = Message;
    type Flags = ();

    fn new(_flags: ()) -> (BigNote, Command<self::Message>) {
        (
            BigNote {
                note: -1,
                need_init: true,
                client_name: String::from("MyBigNote"),
                connection: Option::None,
                ports_list: pick_list::State::<Port>::default(),
                scroll: scrollable::State::default(),
                selected_port: Port { index: 1 },
            },
            Command::none(),
        )
    }

    fn view(&mut self) -> Element<Message> {
        let pick_list = PickList::new(
            &mut self.ports_list,
            Self::ports_names(),  // here is static method
            Some(self.selected_port.clone()),
            Message::PortSelected,
        );

        let mut content = Scrollable::new(&mut self.scroll)
            .width(Length::Fill)
            .align_items(Align::Center)
            .spacing(10)
            .push(Space::with_height(Length::Units(600)))
            .push(Text::new("Which is your favorite String?"))
            .push(pick_list);

        content = content.push(Space::with_height(Length::Units(600)));

        Container::new(content)
            .width(Length::Fill)
            .height(Length::Fill)
            .center_x()
            .center_y()
            .into()
    }
}

如果我只是替换方法签名——问题从«不可变借用不能与可变一起执行»开始,直到«临时资源不能从函数返回»。

extern crate iced;
extern crate midir;

use std::cell::RefCell;
use std::error::Error;
use std::io::{stdin, stdout, Write};
use std::option::Option::Some;
use std::sync::mpsc::{channel, Sender};

use iced::{
    button, executor, pick_list, scrollable, Align, Application, Button, Column, Command,
    Container, Element, Length, PickList, Scrollable, Settings, Space, Text,
};
use midir::{Ignore, MidiInput, MidiInputConnection, MidiInputPort, MidiInputPorts};

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
struct Port {
    pub index: usize,
}

struct BigNote {
    note: i32,
    need_init: bool,
    client_name: String,
    scroll: scrollable::State,
    ports_list: pick_list::State<Port>,
    selected_port: Port,

    connection: Option<MidiInputConnection<Sender<Vec<u8>>>>,
}

impl BigNote {
    fn midi_in(&self) -> MidiInput {
        MidiInput::new(&self.client_name.to_string()).unwrap()
    }
    fn ports_names(&self) -> Vec<Port> { // here is change
        let mut names: Vec<Port> = Vec::new();
        names.push(Port { index: 0 });
        names.push(Port { index: 1 });
        names.into()
    }
}

impl Application for BigNote {
    type Executor = executor::Default;
    type Message = Message;
    type Flags = ();

    fn new(_flags: ()) -> (BigNote, Command<self::Message>) {
        (
            BigNote {
                note: -1,
                need_init: true,
                client_name: String::from("MyBigNote"),
                connection: Option::None,
                ports_list: pick_list::State::<Port>::default(),
                scroll: scrollable::State::default(),
                selected_port: Port { index: 1 },
            },
            Command::none(),
        )
    }

    fn view(&mut self) -> Element<Message> {
        let pick_list = PickList::new(
            &mut self.ports_list,
            self.ports_names(),  // and here
            Some(self.selected_port.clone()),
            Message::PortSelected,
        );

        let mut content = Scrollable::new(&mut self.scroll)
            .width(Length::Fill)
            .align_items(Align::Center)
            .spacing(10)
            .push(Space::with_height(Length::Units(600)))
            .push(Text::new("Which is your favorite String?"))
            .push(pick_list);

        content = content.push(Space::with_height(Length::Units(600)));

        Container::new(content)
            .width(Length::Fill)
            .height(Length::Fill)
            .center_x()
            .center_y()
            .into()
    }
}

请帮助我了解为什么会发生这种情况以及如何避免此类问题。

[编辑]

我觉得,我仍然没有得到 rust 中变量\局部变量的基本概念,因为当我将view函数更改为:

fn view(&mut self) -> Element<Message> {
        let port_names = self.ports_names(); // method call now here
        let pick_list = PickList::new(
            &mut self.ports_list,
            port_names, // here only variable
            Some(self.selected_port.clone()),
            Message::PortSelected,
        );

        let mut content = Scrollable::new(&mut self.scroll)
            .width(Length::Fill)
            .align_items(Align::Center)
            .spacing(10)
            .push(Space::with_height(Length::Units(600)))
            .push(Text::new("Which is your favorite String?"))
            .push(pick_list);

        content = content.push(Space::with_height(Length::Units(600)));

        Container::new(content)
            .width(Length::Fill)
            .height(Length::Fill)
            .center_x()
            .center_y()
            .into()
    }

有效。我知道原因是 - 我将直接方法调用移至本地可行,但我自己看不出有什么区别......

标签: rustborrow-checker

解决方案


每当你有一个复合表达式,如结构构造函数或函数调用,

        let pick_list = PickList::new(
            &mut self.ports_list,
            self.ports_names(),  // and here
            Some(self.selected_port.clone()),
            Message::PortSelected,
        );

对它的求值以一种非常具体的方式进行:首先,每个参数表达式都被求值,按照它们在源代码中的写入顺序,它们的值临时存储在堆栈中。然后,一旦所有参数值都准备好,就调用该函数。

在这种情况下:

  1. &mut self.ports_list首先评估并存储为临时值。现在,self.ports_list是可变借来的。

  2. self.ports_names()第二次被评估——但self.ports_list被可变借用,并且调用ports_names需要对所有self的不可变借用,这是不允许的,因此会出现编译错误。

    在调用 的版本中Self::ports_names(),没有借用self,所以这是允许的。

  3. Some(self.selected_port.clone())在成功编译的版本中被评估为第三。这里,self.selected_port是一个与不同的字段self.ports_list,编译器知道它们可以安全地单独借用。

我将直接方法调用移至本地可行,但我自己看不出有什么区别......

通常,您可以在与其他借用的同时对结构的特定字段进行可变借用,但不能有与其他任何东西重叠的可变借用——并且方法调用总是借用整个结构,保证重叠。

第三个工作版本的重要区别不是self.ports_names()存储在局部变量中,而是self.ports_names()在评估之前 &mut self.ports_list评估。局部变量只是为了方便实现这一点。

例如,如果PickList是您的一种类型,您可以将其编写为结构文字而不是调用new()函数,那么您可以按照不冲突的顺序编写字段初始值设定项:

let pick_list = PickList {
    options: self.ports_names(),
    selected: Some(self.selected_port.clone()),
    on_selected: Message::PortSelected

    // Write this last and it doesn't conflict with any temporary immutable borrows above.
    state: &mut self.ports_list,
}

推荐阅读