如何在成员方法闭包中使用结构体自身。

14
如何在闭包中调用方法?get_access_token方法可以根据self.get_base_url()设置新的访问令牌:
fn fetch_access_token(_base_url: &String) -> String {
    String::new()
}

fn get_env_url() -> String {
    String::new()
}

pub struct App {
    pub base_url: Option<String>,
    pub access_token: Option<String>,
}

impl App {
    pub fn new() -> App {
        App {
            base_url: None,
            access_token: None,
        }
    }
    pub fn get_base_url(&mut self) -> &String {
        self.base_url.get_or_insert_with(|| get_env_url())
    }
    pub fn get_access_token(&mut self) -> &String {
        self.access_token
            .get_or_insert_with(|| fetch_access_token(self.get_base_url()))
    }
}

fn main() {}

错误:

Rust 2015

error[E0500]: closure requires unique access to `self` but `self.access_token` is already borrowed
  --> src/main.rs:26:33
   |
25 |         self.access_token
   |         ----------------- borrow occurs here
26 |             .get_or_insert_with(|| fetch_access_token(self.get_base_url()))
   |                                 ^^                    ---- borrow occurs due to use of `self` in closure
   |                                 |
   |                                 closure construction occurs here
27 |     }
   |     - borrow ends here

Rust 2018

error[E0501]: cannot borrow `self.access_token` as mutable because previous closure requires unique access
  --> src/main.rs:25:9
   |
25 | /         self.access_token
26 | |             .get_or_insert_with(|| fetch_access_token(self.get_base_url()))
   | |______________------------------_--____________________----________________^ second borrow occurs here
   |                |                  |                     |
   |                |                  |                     first borrow occurs due to use of `self` in closure
   |                |                  closure construction occurs here
   |                first borrow later used by call

error[E0500]: closure requires unique access to `self` but it is already borrowed
  --> src/main.rs:26:33
   |
24 |       pub fn get_access_token(&mut self) -> &String {
   |                               - let's call the lifetime of this reference `'1`
25 |           self.access_token
   |           -----------------
   |           |
   |  _________borrow occurs here
   | |
26 | |             .get_or_insert_with(|| fetch_access_token(self.get_base_url()))
   | |_________________________________^^____________________----________________- returning this value requires that `self.access_token` is borrowed for `'1`
   |                                   |                     |
   |                                   |                     second borrow occurs due to use of `self` in closure
   |                                   closure construction occurs here

1
fn get_*(&mut self) - 这不是getter通常的工作方式。为什么不将字段设置为私有,并将它们初始化为您要查找的默认值呢?另外,不要使用&String - 而应该使用&str - Stefan
1
也许 https://vorner.github.io/difficult.html#rust-is-different 对你来说是一个不错的阅读材料。简而言之,你试图用自己习惯的方式解决问题,但这并不适用于 Rust - 如果你告诉我们更多关于实际问题的信息,你可能会得到更好的关于 Rust 解决方案的答案。 - Stefan
谢谢大家。这是从 IRC #rust-beginners 得到的解决方案。<br/> 代码示例请参见 Rust Play Ground Code Link - Aqrun
@Aqrun请不要在评论中放置答案。 您可以在下面添加自己的答案。 如果您觉得不应该为其他人的答案(例如IRC上的任何人)获得信用,您可以选择将答案设为“社区wiki”。 - Shepmaster
4个回答

13

将您的数据和方法拆分成较小的组件,然后可以对self的各个组件进行不相交的借用:

fn fetch_access_token(_base_url: &str) -> String { String::new() }
fn get_env_url() -> String { String::new() }

#[derive(Default)]
struct BaseUrl(Option<String>);

impl BaseUrl {
    fn get(&mut self) -> &str {
        self.0.get_or_insert_with(|| get_env_url())
    }
}

#[derive(Default)]
struct App {
    base_url: BaseUrl,
    access_token: Option<String>,
}

impl App {
    fn new() -> App {
        App::default()
    }

    fn get_access_token(&mut self) -> &str {
        let base_url = &mut self.base_url;
        self.access_token
            .get_or_insert_with(|| fetch_access_token(base_url.get()))
    }
}

fn main() {}

你可以进一步将此操作用于两个值:

fn fetch_access_token(_base_url: &str) -> String { String::new() }
fn get_env_url() -> String { String::new() }

#[derive(Default)]
struct BaseUrl(Option<String>);

impl BaseUrl {
    fn get(&mut self) -> &str {
        self.0.get_or_insert_with(|| get_env_url())
    }
}

#[derive(Default)]
struct AccessToken(Option<String>);

impl AccessToken {
    fn get(&mut self, base_url: &str) -> &str {
        self.0.get_or_insert_with(|| fetch_access_token(base_url))
    }
}

#[derive(Default)]
struct App {
    base_url: BaseUrl,
    access_token: AccessToken,
}

impl App {
    fn new() -> App {
        App::default()
    }

    fn get_access_token(&mut self) -> &str {
        let base_url = self.base_url.get();
        self.access_token.get(base_url)
    }
}

fn main() {}

这让您可以抽象出常见的功能:

fn fetch_access_token(_base_url: &str) -> String { String::new() }
fn get_env_url() -> String { String::new() }

#[derive(Default)]
struct StringCache(Option<String>);

impl StringCache {
    fn get<F>(&mut self, f: F) -> &str
    where
        F: FnOnce() -> String,
    {
        self.0.get_or_insert_with(f)
    }
}

#[derive(Default)]
struct App {
    base_url: StringCache,
    access_token: StringCache,
}

impl App {
    fn new() -> App {
        App::default()
    }

    fn get_access_token(&mut self) -> &str {
        let base_url = self.base_url.get(get_env_url);
        self.access_token.get(|| fetch_access_token(base_url))
    }
}

fn main() {}

然后你意识到这种抽象可以被泛化:

fn fetch_access_token(_base_url: &str) -> String { String::new() }
fn get_env_url() -> String { String::new() }

#[derive(Default)]
struct Cache<T>(Option<T>);

impl<T> Cache<T> {
    fn get<F>(&mut self, f: F) -> &T
    where
        F: FnOnce() -> T,
    {
        self.0.get_or_insert_with(f)
    }
}

#[derive(Default)]
struct App {
    base_url: Cache<String>,
    access_token: Cache<String>,
}

impl App {
    fn new() -> App {
        App::default()
    }

    fn get_access_token(&mut self) -> &str {
        let base_url = self.base_url.get(get_env_url);
        self.access_token.get(|| fetch_access_token(base_url))
    }
}

fn main() {}

另请参阅:


1

这里提供了一个答案(其他地方)。但是在2022年,您可能会更好地使用Rc<Self>Arc<Self>并通过Rc::clone(&self)Arc::clone(&self)进行克隆。请查看此处以获取有关self接收器类型的官方文档。

fn fetch_access_token(_base_url: &String) -> String {
    String::new()
}

fn get_env_url() -> String {
    String::new()
}

pub struct App {
    pub base_url: Option<String>,
    pub access_token: Option<String>,
}

impl App {
    pub fn new() -> App {
        App {
            base_url: None,
            access_token: None,
        }
    }
    pub fn get_base_url(self : Rc<Self>) -> &String {
        let me = Rc::clone(&self);
        me.base_url.get_or_insert_with(|| get_env_url())
    }
    pub fn get_access_token(self : Rc<Self>) -> &String {
        let me = Rc::clone(&self);
        let other_me = Rc::clone(&self);
        me.access_token
            .get_or_insert_with(|| fetch_access_token(other_me.get_*))
    }
}

fn main() {}

1
Option<T>中,传递给get_or_insert_with方法的闭包属于FnOnce类型 - 因此它会消耗或移动捕获的变量。在这种情况下,由于在闭包中使用了self.get_base_url(),因此会捕获self。但是,由于self已经被借用,闭包不能消耗或移动self的值以进行唯一访问。
可以通过使用get_or_insert方法来规避这个问题,但这将要求您每次调用get_access_token时执行可能昂贵的获取访问令牌操作,无论access_token是否为None

谢谢,但正如你所说,我认为在Rust风格中解决这种问题可能有更有效的方法或另一种代码结构。 - Aqrun

1
我建议使用类似以下的内容:

游乐场

fn fetch_access_token(base_url: &str) -> Result<String, ()> {
    let _url = format!("{}/v3/auth/token", base_url);
    // ...
    let token = String::from("test token");
    Ok(token)
}

fn get_env_url() -> String {
    String::from("http://www.test.com")
}

pub struct App {
    // private fields!
    base_url: String,
    access_token: Option<String>,
}

impl App {
    pub fn new() -> App {
        App {
            base_url: get_env_url(),
            access_token: None,
        }
    }

    /// set new base url; clears cached access token
    pub fn set_base_url(&mut self, base_url: String) {
        self.base_url = base_url;
        self.access_token = None;
    }

    pub fn get_base_url(&self) -> &str {
        &self.base_url
    }

    /// retrieve (possibly cached) access token. tries again if previous attempt failed.
    pub fn retrieve_access_token(&mut self) -> Result<&str, ()> {
        if self.access_token.is_none() {
            self.access_token = Some(fetch_access_token(&self.base_url)?);
        }
        Ok(self.access_token.as_ref().unwrap())
    }
}

fn main() {
    let mut app = App::new();
    println!("{}", app.retrieve_access_token().unwrap());
}

1
这个可以工作,但是一个永远不会失败的 unwrap 总是让我感到难过。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接