DEV Community

Adam Crockett 🌀
Adam Crockett 🌀

Posted on

Struct callback.

I am writing a grammer defined as a bTreeMap, the key is the token and the value is a GrammerDefinition struct. The struct in question stores metadata about a specific token, such as the error name, rules about what is allowed to follow a token and so on. I want to be able to optionally set a callback to handle errors and rules of said metadata. Is this the most optimal pattern and how might this look in code? Any help would be greatly appreciated, I have been googling for around a month.

Top comments (10)

Collapse
 
deciduously profile image
Ben Lovy • Edited

I don't know about most optimal, but I've had success using an Rc:

pub struct Callback<T> {
    f: Rc<dyn Fn() -> T>,
}

impl<T> Callback<T> {
    /// Call this callback
    pub fn call(&self) -> T {
        (self.f)()
    }
}

impl<T> Clone for Callback<T> {
    fn clone(&self) -> Self {
        Self {
            f: Rc::clone(&self.f),
        }
    }
}

impl<T, F: Fn() -> T + 'static> From<F> for Callback<T> {
    fn from(func: F) -> Self {
        Self { f: Rc::new(func) }
    }
}

You'd need to adjust the signatures to match your criteria. Construct with, e.g., Callback::from(|| -> T {//...}).

Does this seem like it'd fit your usage?

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀 • Edited

Ben to the rescue again, I will give it a shot (and read about RC) as soon as I'm home. Currently on a crowded train trying to look busy 😅

Collapse
 
deciduously profile image
Ben Lovy

My Rust senses were tingling :)

Thread Thread
 
adam_cyclones profile image
Adam Crockett 🌀 • Edited

Hey Ben, I am just revisiting this.

pub struct Callback<T> {
    f: Rc<dyn Fn(&str) -> T>,
}

impl<T> Callback<T> {
    /// Call this callback
    pub fn call(&self, token: &str) -> T {
        return (self.f)(token);
    }
}

impl<T> Clone for Callback<T> {
    fn clone(&self) -> Self {
        Self {
            f: Rc::clone(&self.f),
        }
    }
}

impl<T, F: Fn(&str) -> T + 'static> From<F> for Callback<T> {
    fn from(func: F) -> Self {
        Self { f: Rc::new(func) }
    }
}

struct ASTNode {
    id: GRAM,
    allow_next: Vec<GRAM>,
    foo: &'static str,
    matcher: Callback<bool>
}


// Usage

fn is_import_statement (token: &str) -> bool {    
    // TBA
    return true;
}

let x = ASTNode{
    id: GRAM::IMPORT_STATEMENT,
    foo: "hey",
    identify_statement_from_next: vec!(
        GRAM::L_C_BRACE,
        GRAM::L_P_BRACKET,
        GRAM::NAME,
        GRAM::ASTERISK
    ),
    matcher: Callback::from(is_import_statement)
};

x.matcher.call("@import");

How can I access self.foo from is_import_statement where self which comes from the ASTNode struct.

Thread Thread
 
deciduously profile image
Ben Lovy • Edited

Hrm. At a glance, I'm not positive you can nor should be able to. The callback won't be able to access any context not provided at instantiation, you'd need to accept an additional argument like dyn Fn(&str,&str), which is usually how I skirt around this sort of thing in Rust. The callback has no concept of any parent object that owns it. I might not fully understand the context here, though - there always is a way to do what you want but not always with the structure you thought.

Do you have a fuller code sample I could play with? Might not get there tonight but can play with it this weekend.

Maybe the callback struct could also contain a &'a ASTNode referencing the parent, passed in to the constructor, but it gets messy. You'd probably shift away from the From trait and just provide a Callback::new().

Thread Thread
 
adam_cyclones profile image
Adam Crockett 🌀

Oh I made a few tweeks and ended up doing this

x.matcher.call(&x ,"@import");

so thats kind of what I wanted... not as nice a real relationship between the callback and the struct it lives in.

Thread Thread
 
deciduously profile image
Ben Lovy

yup, that was my first thought but I agree, not what you're gunning for. Maybe try the stored reference?

Thread Thread
 
adam_cyclones profile image
Adam Crockett 🌀 • Edited

I think I can live with it tbh, it works well. Ultimately I can progress to the next bit of Jess's lexer. This part of the code isn't committed yet, the whole solution is a bit higldy pigldy. Sorry to rubber duck, I find writing about problems is enormously helpful.

I will check out the communities you suggested and such 😁.

Thank you so much once again.

Thread Thread
 
deciduously profile image
Ben Lovy

Don't apologise! I get a lot out of it too, and think you've got a cool project going on.

 
deciduously profile image
Ben Lovy • Edited

Also, the /r/learnrust subreddit is small, but generally helpful, and the main /r/rust subreddit may help you too. They're pretty intense, but highly knowledgeable and you may be pushing the edge of what I understand - not that I'm not curious to learn this solution.