Closure carnage

⚓ Rust    📅 2025-12-31    👤 surdeus    👁️ 4      

surdeus

Info

This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Closure carnage

Introduction

I know to not do with a macro something functions can.
My previous declarative macro didn't use any of the declarative macro special features so I decided to make it into a function.

impl MyApp {
    /// Philosophy: shrink if possible - expand otherwise
    fn selected_cell_range_arrow_key<MainAxis: Copy + PartialOrd<MainAxis>, OtherAxis>(
        &mut self, selection_state: (Column, Row, &ColumnRangeInclusive, &RowRangeInclusive),
        input: (&InputState, bool, Key), end_action: impl Fn(MainAxis) -> MainAxis, limit: MainAxis,
        switch_axis: (
            impl Fn(Column, Row) -> (MainAxis, OtherAxis),
            impl for<'a> Fn(&'a ColumnRangeInclusive, &'a RowRangeInclusive) -> (&'a RangeInclusive<MainAxis>, &'a RangeInclusive<OtherAxis>),
            impl for<'a> Fn(&'a RangeInclusive<MainAxis>, &'a RangeInclusive<OtherAxis>) -> (&'a ColumnRangeInclusive, &'a RowRangeInclusive),
            impl Fn(Option<MainAxis>, Option<OtherAxis>) -> (Option<Column>, Option<Row>),
            impl Fn(MainAxis, OtherAxis) -> (Column, Row),
        ),
        switch_end: (
            impl Fn(MainAxis, MainAxis) -> (MainAxis, MainAxis),
            impl Fn((MainAxis, MainAxis), (MainAxis, MainAxis)) -> ((MainAxis, MainAxis), (MainAxis, MainAxis)),
        ),
    ) -> (Option<Column>, Option<Row>) {
        let (column, row, column_range, row_range) = selection_state;
        let (i, shift, arrow) = input;
        let (
            switch_axis_a, switch_axis_b,
            switch_axis_c, switch_axis_d,
            switch_axis_e,
        ) = switch_axis;
        let (switch_end_a, switch_end_b) = switch_end;
        // The axis refers to wether it's top-bottom or left-right.
        // The end refers to wether it's positive(right-bottom) or negative(left-top). 
        if i.key_pressed(arrow) {
            let (main_axis, _) = switch_axis_a(column, row);
            let (no_shift_left, no_shift_right) = switch_end_a(main_axis, limit);
            if shift {
                let (main_axis_range, other_axis_range) = switch_axis_b(column_range, row_range);
                // This is a pattern to decide wich end is mutated from a closure that could switch or not.
                let (mut main_end, mut other_end) = switch_end_a(*main_axis_range.start(), *main_axis_range.end());
                let ((shrink_left, expand_left), (shrink_right, expand_right)) =
                    switch_end_b((main_end, other_end), (main_axis, limit));
                // The main end is the one that gets changed when shrinking
                let changed_end = if shrink_left < shrink_right {
                    main_end = end_action(main_end);
                    main_end
                // The other end is the one that gets changed when expanding
                } else if expand_left < expand_right {
                    other_end = end_action(other_end);
                    other_end
                } else {
                    // No column and no row has been changed
                    return (None, None)
                };
                // Now that the correct end has been mutated, the original order can be restored with another switch.
                let (start, end) = switch_end_a(main_end, other_end);
                let main_axis_range = range!(start,,;end);
                let (column_range, row_range) = switch_axis_c(&main_axis_range, other_axis_range);
                self.set_selection_range(column, row, column_range.clone(), row_range.clone());
                return switch_axis_d(Some(changed_end), None)
            } else if no_shift_left < no_shift_right {
                // Same pattern
                let (main_axis, other_axis) = switch_axis_a(column, row);
                let main_axis = end_action(main_axis);
                let (column, row) = switch_axis_e(main_axis, other_axis);
                self.select_cell(column, row);
                return (Some(column), Some(row))
            }
        }
        (None, None)
    }
}

Problem

As you could probably tell, what was originally only one expresssion in the macro has turned into many closures with the same job but different types in the function. I had to resort to another declarative macro to keep the code dry.


impl MyApp {
    fn selected_cell_range_keyboard_navigation_got_input(
        &mut self,
        ctx: &Context,
        cell: (Column, Row),
        cell_range: (ColumnRangeInclusive, RowRangeInclusive),
        total_cells: (Column, Row),
    ) -> (Option<Column>, Option<Row>) {
        let (column, row) = cell;
        let (column_range, row_range) = cell_range;
        let (total_columns, total_rows) = total_cells;
        ctx.input(|i| {
            let shift = i.modifiers.shift;
            macro_rules! selected_cell_range_arrow_key {
                (
                    arrow = $arrow:ident, end_action = $end_action:expr, limit = $limit:expr;
                    switch_axis = $switch_axis:expr, switch_end = $switch_end:expr;
                ) => {
                    match self.selected_cell_range_arrow_key(
                        (column, row, &column_range, &row_range), (i, shift, Key::$arrow), $end_action, $limit,
                        ($switch_axis, $switch_axis, $switch_axis, $switch_axis, $switch_axis), ($switch_end, $switch_end),
                    ) {
                        (None, None) => {}
                        (column, row) => return (column, row),
                    }
                };
            }
            selected_cell_range_arrow_key!(
                arrow = ArrowUp, end_action = Row::previous, limit = Row::first();
                switch_axis = |right, left| (left, right), switch_end = |right, left| (left, right);
            );
            selected_cell_range_arrow_key!(
                arrow = ArrowDown, end_action = Row::next, limit = total_rows.previous();
                switch_axis = |right, left| (left, right), switch_end = |left, right| (left, right);
            );
            selected_cell_range_arrow_key!(
                arrow = ArrowLeft, end_action = Column::previous, limit = Column::first();
                switch_axis = |left, right| (left, right), switch_end = |right, left| (left, right);
            );
            selected_cell_range_arrow_key!(
                arrow = ArrowRight, end_action = Column::next, limit = total_columns.previous();
                switch_axis = |left, right| (left, right), switch_end = |left, right| (left, right);
            );
            (None, None)
        })
    }
}

Question

Is there another way to reduce code repetition, macro use while avoiding dyn?

Here are the rest of the definitions:

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Column(usize);
impl Column {
    pub const fn new(column_index: usize) -> Self {
        Self(column_index)
    }
    pub const fn first() -> Self {
        Self(0)
    }
    pub const fn index(self) -> usize {
        self.0
    }
    pub const fn previous(self) -> Self {
        Self(self.0 - 1)
    }
    pub const fn next(self) -> Self {
        Self(self.0 + 1)
    }
    pub const fn mid(low: Self, high: Self) -> Self {
        Self(low.0 + (high.0 - low.0) / 2)
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Row(usize);
impl Row {
    pub const fn new(row_index: usize) -> Self {
        Self(row_index)
    }
    pub const fn first() -> Self {
        Self(0)
    }
    pub const fn index(self) -> usize {
        self.0
    }
    pub const fn previous(self) -> Self {
        Self(self.0 - 1)
    }
    pub const fn next(self) -> Self {
        Self(self.0 + 1)
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FillDirection {
    Top(Row),
    Right(Column),
    Bottom(Row),
    Left(Column),
}
#[macro_export]
macro_rules! range {
    ($start:expr,,;$end:expr) => {
        RangeInclusive::from($start, $end)
    };
}
#[derive(Clone, Default, PartialEq, Eq, Hash)]
pub struct RangeInclusive<Idx> {
    start: Idx,
    end: Idx,
    exhausted: bool,
}

impl<Idx> RangeInclusive<Idx> {
    #[inline]
    pub const fn from(start: Idx, end: Idx) -> Self {
        Self { start, end, exhausted: false }
    }
    #[inline]
    pub const fn start(&self) -> &Idx {
        &self.start
    }
    #[inline]
    pub const fn end(&self) -> &Idx {
        &self.end
    }
}

pub type ColumnRangeInclusive = RangeInclusive<Column>;
pub type RowRangeInclusive = RangeInclusive<Row>;
#[derive(PartialEq, Eq)]
struct SelectionRange(ColumnRangeInclusive, RowRangeInclusive, Option<FillDirection>);
#[derive(PartialEq, Eq)]
struct SelectedCell(Column, Row, Option<SelectionRange>);
#[derive(PartialEq, Eq)]
enum CellState {
    SelectedCell(SelectedCell)
}

pub struct MyApp {
    cell_state: CellState,
}
#[derive(Clone)]
pub struct Context(InputState);
impl Context {
    #[inline]
    pub fn input<R>(&self, reader: impl FnOnce(&InputState) -> R) -> R {
        reader(&self.0)
    }
}
#[derive(Clone, Debug)]
pub struct InputState {
    pub modifiers: Modifiers,
    key_pressed: Key,
}
impl InputState {
    pub fn key_pressed(&self, desired_key: Key) -> bool {
        self.key_pressed == desired_key
    }
}
#[derive(Clone, Debug, Copy, Default, Hash, PartialEq, Eq)]
pub struct Modifiers {
    pub alt: bool,
    pub ctrl: bool,
    pub shift: bool,
    pub command: bool,
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub enum Key {
    ArrowDown,
    ArrowLeft,
    ArrowRight,
    ArrowUp,
}
impl MyApp {
    pub const fn set_selection_range(&mut self, column: Column, row: Row,
        column_range: ColumnRangeInclusive, row_range: RowRangeInclusive)  {
        self.cell_state = CellState::SelectedCell(SelectedCell(column, row, Some(
            SelectionRange(column_range, row_range, None)
        )));
    }
    pub const fn select_cell(&mut self, column: Column, row: Row) {
        self.cell_state = CellState::SelectedCell(SelectedCell(column, row, None));
    }
}

3 posts - 2 participants

Read full topic

🏷️ Rust_feed