How do you categorize methods on a struct with too many methods

We have a struct with about 50 methods, each for an endpoint, that can be categorized because it has methods like send_message delete_message edit_message, so how would you go around doing this with minimum overhead? (The methods are somewhat similar to Client in twilight_http::client - Rust)

A possible solution would be something like

impl Context {
    pub fn message<'ctx>(&'ctx self) -> MessageContext<'ctx> {
        MessageContext(&self)
    }
}

struct MessageContext<'ctx>(&'ctx Context);

impl<'ctx> MessageContext<'ctx> {
    pub async fn send(…
}

But that’s a lot of boilerplate especially because of the lifetimes so is there a better solution?

Most of the time, when you've got lots of methods like this you'll notice they all follow a similar pattern in a fairly boring way.

If that's the case, you could use code generation to generate the methods and categories from some higher-level spec. That's what the AWS SDK and Google APIs projects do, for example.

This code generation could take the form of Rust code that literally generates the code as a string from some definition file or it could be a macro_rules! macro.

It's really up to you which approach you take, although I prefer to generate source code from a Rust function because it lets you check the generated code into version control and view it. Here is an example from another project where I generate code for some AST stuff and make sure it's in sync with syntax_kind.rs and ast.rs.

2 Likes

The MessageContext solution seems like the right approach to me. Except maybe find a more descriptive name like Endpoint.

It doesn't look like a lot of boilerplate, you're basically just grouping the methods into groups.

A possible benefit of this approach might be that perhaps for some endpoints there is some information, so the struct is not always empty?

I don't really understand it, we don't have a spec or anything, it's all handwritten

Will I ever run into lifetime issues with this in an async context?

The compiler support for async fn supports holding references across await, that's why it needs all the Pin machinery (IIRC), so you shouldn't see any additional lifetime issues.

1 Like

The places where lifetimes come up in async/await are generally the same places as where they come up in ordinary non-async code that uses multiple threads.