How to use struct self in member method closure

后端 未结 3 492
抹茶落季
抹茶落季 2020-11-28 16:12

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_         


        
相关标签:
3条回答
  • 2020-11-28 16:34

    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:

    • Borrowing references to attributes in a struct
    • Why is it discouraged to accept a reference to a String (&String), Vec (&Vec) or Box (&Box) as a function argument?
    • The Rust Programming Language chapter on closures, which creates this caching struct as part of the exercises.
    0 讨论(0)
  • 2020-11-28 16:43

    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.

    0 讨论(0)
  • 2020-11-28 16:44

    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());
    }
    
    0 讨论(0)
提交回复
热议问题