How to use struct self in member method closure

大兔子大兔子 提交于 2019-12-02 01:01:57

Split your data and methods into smaller components, then you can take disjoint borrows to various components on 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() {}

You can go further and do this for both values:

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() {}

Which lets you see that you can abstract out common functionality:

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() {}

And then you realize the abstraction can be made generic:

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() {}

See also:

I'd use something like this instead:

Playground

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());
}

The closure passed to the get_or_insert_with method in Option<T> is of type FnOnce - it thus consumes or moves the captured variables. In this case self is captured because of the usage of self.get_base_url() in the closure. However, since self is already borrowed, the closure cannot consume or move the value of self for unique access.

This can be circumvented by using the get_or_insert method, but it will require you to perform the potentially expensive operation of fetching the access token every time get_access_token is called regardless of whether access_token is None or not.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!