Generics / Traits in function argument repetition

I'm wondering if there is a way to name the type (ideally) or somehow reduce the repetition here, specifically the closure arguments to page. The other places are not important.

fn page<B, F>(w: &mut dyn ::std::io::Write, body: B, footer: F) -> ::std::io::Result<()>
where
    B: FnOnce(&mut dyn ::std::io::Write) -> ::std::io::Result<()>,
    F: FnOnce(&mut dyn ::std::io::Write) -> ::std::io::Result<()>,
{
    w.write(b"begin body\n")?;
    body(w)?;
    w.write(b"begin footer\n")?;
    footer(w)?;
    Ok(())
}

fn main() {
    let body = |w: &mut dyn ::std::io::Write| -> ::std::io::Result<()> {
        w.write(b"BODY\n")?;
        Ok(())
    };
    let footer = |w: &mut dyn ::std::io::Write| -> ::std::io::Result<()> {
        w.write(b"FOOTER\n")?;
        Ok(())
    };
    let mut out = vec![];
    page(&mut out, body, footer).unwrap();
    println!("{}", String::from_utf8(out).unwrap());
}

(Playground)

What I would like very much is something like this:

type Render = FnOnce(&mut dyn ::std::io::Write) -> ::std::io::Result<()>;

fn page<B, F>(w: &mut dyn ::std::io::Write, body: Render, footer: Render) -> ::std::io::Result<()> {

I found the unstable trait_alias feature, which maybe does more than I need. I need something on stable though, so I'm wondering if there's a way to achieve that for my use case.

What about introducing an intermediate trait which closures automatically implement?

trait Render {
    fn render(self, destination: &mut dyn Write) -> Result<()>;
}

impl<F> Render for F
where
    F: FnOnce(&mut dyn Write) -> Result<()>,
{
    fn render(self, destination: &mut dyn Write) -> Result<()> {
        self(destination)
    }
}

(playground)


Also, this isn't necessarily related to the problem, but unless you're writing macro code it's often easier to use std::io::{Write, Result} at the top of the file instead of using their fully-qualified paths.

Usually it's pretty obvious that anything involving a Write trait will be using things from std::io, so you can improve readability by removing the leading ::std::io::'s.

Thank you! This is exactly what I was looking for. Most of this is macro output as you surmised, which is why the avoidance of use.

1 Like

If you're using macros to generate the various textual elements you might consider (possibly zero-sized) creating structs which implement Render.

That way users can see what elements are available by viewing API docs, although that's probably not necessary if the closures are temporaries and the main way you generate the writes is by invoking the macro directly.

Thanks for the suggestion -- though if I understand correctly it won't apply to my use case (I need to optionally capture local environment). It's just an experiment so far, but it's open source if you're curious: qtpl - Rust!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.