I ported the Monoid (Semigroup) keylogger from Haskell to Rust

The idea is taken from Equational Reasoning at Scale.

Hopefully this can be a stepping stone of mine to understand warp::Filter.

In playground this code just outputs "I ACTUALLY DID IT" with a space in front of each letter, but if comment out the hardcoded cs and the match to stop the loop, it accepts keyboard input and logs into two files as specified.


trait Semigroup {
    fn op(self, other: Self) -> Self;

impl Semigroup for () {
    fn op(self, _other: Self) {}

impl<A, B> Semigroup for Box<dyn Fn(A) -> B + '_>
    A: Clone + 'static,
    B: Semigroup + 'static,
    fn op(self, other: Self) -> Self {
        Box::new(move |a| self(a.clone()).op(other(a)))

use std::io::{Read, Write};

type Plugin<'a> = Box<dyn Fn(u8) + 'a>;

fn log_to(file_path: &std::path::Path) -> Plugin<'_> {
    Box::new(move |c: u8| {
        let mut handle = std::fs::OpenOptions::new()

fn main() {
    let file_path1 = std::path::Path::new("file1.txt");
    let file_path2 = std::path::PathBuf::from("file2.txt");
    let handle_char = log_to(file_path1).op(log_to(file_path2.as_ref())).op(Box::new(|c| {
        std::io::stdout_locked().write_all(&[b' ']).unwrap()
    let mut cs = "I ACTUALLY DID IT".bytes();
    loop {
        // let c = std::io::stdin_locked().bytes().next().unwrap().unwrap();
        let c = match cs.next() {
            Some(c) => c,
            None => break,
 I   A C T U A L L Y   D I D   I T

Update 02/20/2022: Generalize the impl to non-static lifetimes

So that's how to name the lifetime of the capture! I feel enlightened.

1 Like

You can slightly generalize that impl by writing

impl<A, B> Semigroup for Box<dyn Fn(A) -> B + '_>

or equivalently

impl<'a, A, B> Semigroup for Box<dyn Fn(A) -> B + 'a>

This way, your code also works with calls to log_to with a non-static &'a Path reference argument.


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.