Search code examples
rustclosuresborrow-checkermutability

How to use struct self in member method closure


How can I call a method in closure? get_access_token method can set new access token based on 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() {}

error:

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

Solution

  • 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: