0

I am facing an error that I have failed to understand properly, maybe I have insufficient knowledge on closure and there proper use. The error is can't capture dynamic environment in a fn item use the `|| { ... }` closure form instead I have defined closures for the handle_app_add_new_app in the AppParams impl as well. All code has been shared below.

main.rs

use std::collections::HashMap;
use std::fs::OpenOptions;
use std::{str, io::Write as write};
use std::env;
mod utils;
use utils::*;

fn main() {
    let args: Vec<String> = env::args().collect();
    let chain_id = args[0];
    let chain_address = args[1];
    let appname = args[2];   
    let abi_endpoint = args[3];
    let app_address = args[4];
    let token_name = args[5];
    let token_address = args[6];

    let new_token = &TokenParams::new(&token_name, &token_address);
    let mut chain_tokens = HashMap::new();
    // Add new tokens to chain.
    chain_tokens.insert(
        new_token.token_name,
        new_token.token_address,
    );
    
    let app = AppParams::new(
        &appname, &abi_endpoint, &app_address, &chain_tokens
    );

    pub unsafe fn convert_to_u8<T: Sized>(p: &T) -> &[u8] {
        ::std::slice::from_raw_parts(
            (p as *const T) as *const u8,
            ::std::mem::size_of::<T>(),
        )
    }
    //add new application(dex,payments...etc)
    pub async fn handle_add_new_app(chain_id: &str, chain_address: &str,) -> Box<dyn Fn(ChainParams) -> ChainParams>{
        let new_app= Box::new(|x| ChainParams::new(
            &chain_id, 
            &chain_address, 
            app,
        ));
        
        let bytes: &[u8] = unsafe {convert_to_u8(&new_app) };
        let mut updated_file = OpenOptions::new().append(true).open("config.json").expect("Unable to open file"); 
        updated_file.write_all(bytes).expect("adding app to config failed");
        new_app

    }
}

The utils file


use std::{error::Error, collections::HashMap};
use serde_json::Value;

#[derive(serde::Deserialize)]
pub struct Addresses{
    pub to_address: String,
    pub from_address: String,
}

impl Addresses{}

#[derive(serde::Deserialize)]
pub struct TokenParams {
    pub token_name: String,
    pub token_address: String,
}

impl TokenParams{
    /// create a new token instance
    pub  fn new(token_name: &str, token_address: &str) -> Self {
        let token_name = token_name;
        let token_address = token_address;
        TokenParams {
            token_address: token_address.to_string(),
            token_name: token_name.to_string(),
        }
    }

    pub fn get_token(chain: &str, app: &str) -> Result<Vec<Value>, Box<dyn Error>>{
        let token_getter = {
            // Load the first file into a string.
            let config = std::fs::read_to_string("Config.json").unwrap();
    
            // Parse the string into a dynamically-typed JSON structure.
            serde_json::from_str::<Value>(&config).unwrap()
        };
        // Get the get tokens with chain and app(eg dex)
        let chain_elements = token_getter[chain][app].as_array().unwrap();
        Ok(chain_elements.to_vec())
    }
}

#[derive(serde::Deserialize)]
pub struct AppParams {
    pub appname: String,
    pub abi_endpoint: String,
    pub app_address: String,
    #[serde(flatten)]
    pub tokens: HashMap<String, String>
}

impl AppParams{
    /// create a new App(dex, marketplace...etc) instance
    pub fn new(appname: &str, abi_endpoint: &str, address: &str, tokens: &HashMap<String, String>) -> Box<dyn Fn(AppParams) -> AppParams> {
        let appname = appname;
        let abi_endpoint = abi_endpoint;
        let address = address;
        let mut app_tokens = HashMap::new();
        Box::new(|x| AppParams {
            appname: appname.to_string(),
            abi_endpoint: abi_endpoint.to_string(),
            tokens: app_tokens,
            app_address: address.to_string(),
        })
    }

    pub fn get_app_params(self, chain: &str) -> Result<Vec<Value>, Box<dyn Error>>{
        let app_getter = {
            // Load the first file into a string.
            let config = std::fs::read_to_string("Config.json").unwrap();
    
            // Parse the string into a dynamically-typed JSON structure.
            serde_json::from_str::<Value>(&config).unwrap()
        };
        // Get the get tokens with chain and app(eg dex)
        let chain_elements = app_getter[chain][self.appname].as_array().unwrap();
        Ok(chain_elements.to_vec())
    }
}

#[derive(serde::Deserialize)]
pub struct ChainParams {
    pub chain_id: String,
    pub chain_address: String,
    #[serde(flatten)]
    pub chain_application: Vec<AppParams>,
}

impl ChainParams{

    /// get an App(dex, marketplace...etc) instance
    pub fn new(chain_id: &str, chain_address: &str, chain_application: AppParams ) -> Self {
        let chain_id = chain_id;
        let chain_address = chain_address;
        let new_tokens = HashMap::new();
        let new_application = AppParams::new(
                &chain_application.appname, 
                &chain_application.abi_endpoint, 
                &chain_application.app_address, 
                &new_tokens,
        );
        ChainParams {
            chain_id: chain_id.to_string(),
            chain_address: chain_address.to_string(),
            chain_application: vec![new_application(chain_application)],
        }
    }

    //get chain params
    pub fn get_chain_params(self) -> Result<Vec<Value>, Box<dyn Error>>{
        let app_getter = {
            // Load the first file into a string.
            let config = std::fs::read_to_string("Config.json").unwrap();
    
            // Parse the string into a dynamically-typed JSON structure.
            serde_json::from_str::<Value>(&config).unwrap()
        };
        // Get the get tokens with chain and app(eg dex)
        let chain_elements = app_getter[self.chain_id].as_array().unwrap();
        Ok(chain_elements.to_vec())
    }


}

A detailed explanation of your answer will be highly appreciated.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
master chief
  • 35
  • 1
  • 7
  • What are you trying to do with `convert_to_u8`? – Dogbert Jun 06 '22 at 07:14
  • 1
    Please always post the full error. And minimized example will be greatly appreciated. – Chayim Friedman Jun 06 '22 at 20:53
  • Does this answer your question? [Unable to create a local function because "can't capture dynamic environment in a fn item"](https://stackoverflow.com/questions/42895596/unable-to-create-a-local-function-because-cant-capture-dynamic-environment-in) – Chayim Friedman Jun 06 '22 at 20:56

1 Answers1

2

Minimized example, produced by removing everything irrelevant to the error:

fn main() {
    let app = ();
    
    pub fn captures_app() {
        let _ = app;
    }
}

Playground

The problem here is the fact that the functions are just functions, they doesn't hold any state. Therefore, they can't refer to anything other then their arguments and static variables, even if they're placed inside another function; the placement governs only the name scoping, i.e. "where this identifier will refer to this function", not the access to the environment.

To see why this is a problem, consider this:

fn returns_fn() -> fn() {
    let app = String::from("app");
    
    pub fn captures_app() {
        println!("{}", app);
    }
    
    captures_app
}

Here, fn() is a function pointer. Every function can be coerced to the function pointer, therefore it must be callable by using the pointer only - but it depends on the app string, which can very well be different for every returns_fn's call and therefore can't be integrated into the returned pointer.


Now, what to do about it? There are two possible courses of action.

You can pass everything the function needs via its arguments. That is, the first example would become:

fn main() {
    let app = ();

    pub fn captures_app(app: ()) {
        let _ = app;
    }
}

and your own code will look as something like this:

pub async fn handle_add_new_app(
    chain_id: &str,
    chain_address: &str,
    app: <return type of AppParams::new>
) -> Box<dyn Fn(ChainParams) -> ChainParams> {
    // implementation
}

Or you can make the function a closure instead. In minimized example, it'll be like this:

fn main() {
    let app = ();

    let captures_app = || {
        let _ = app;
    }
}

In your code, that's slightly more tricky, because async closures are unstable yet, but you can do it like this:

let handle_add_new_app = move |chain_id: &str, chain_address: &str| -> Box<dyn Fn(ChainParams) -> ChainParams> {
    async move {
        // implementation
    }
}
Cerberus
  • 8,879
  • 1
  • 25
  • 40