Runtime error: thread 'main' panicked at 'StringComponentStore.get_mut: internal downcast error'

Hello

I'm getting the following error at runtime after pressing the =-button:

thread 'main' panicked at 'StringComponentStore.get_mut: internal downcast error', /Users/lol/.cargo/git/checkouts/dces-rust-0e90bfa1cf1cf6e4/fb2c263/src/component/string_component_store.rs:249:21:

With the following code:

use orbtk::*;

/// Module holding all the mathematical logic to actually calculate a result
mod logic {
    use std::str::FromStr;
    
    // Trait to allow subscription access to chars in Strings using ch function.
    trait StringSubscription {
        fn ch(&self, pos: usize) -> char; 
    }
    impl StringSubscription for String {
        fn ch(&self, pos: usize) -> char {
            self.chars().nth(pos).unwrap()
        }
    }



    /**
     * Function to check if given character is an operator or not.
     *
     * # Arguments
     * * `c` - Character to check.
     *
     * # Return
     * Boolean. Returns true if `c` is an operator.
     *
     * # Example
     * ```
     * assert_eq!(is_operator('+'), true);
     * assert_eq!(is_operator('x'), true);
     * ```
     */
    fn is_operator(c: char) -> bool {
        let operators : [char; 4] = ['+', '-', '*', '/'];
        if operators.contains(&c) {
            true
        } else {
            false
        }
    }

    /**
     * Function to get precedence of an operator. This to determine
     * which operator to evaluate first.
     *
     * # Arguments
     * * `operator` - Char. Operator to check
     *
     * # Return
     * i8. Returns an integer representing the precedence of the `operator`.
     *
     * # Example
     * ```
     * assert_eq!(precedence('+') < precedence('*'), true);
     * assert_eq!(precedence('*') == precedence('/'), true);
     * assert_eq!(precedence('-') >= precedence('/'), false);
     * ```
     */
    fn precedence(operator: char) -> i8 {
        match operator {
            '+' => 1,
            '-' => 1,
            '*' => 2,
            '/' => 2,
            _ => -1,
        }
    }

    /**
     * Function to convert an infix string to postfix notation.
     *
     * # Arguments
     * * `infix` - String containing the infix.
     *
     * # Return
     * Returns a string in postfix notation.
     *
     * # Example
     * ```
     * let s : String = "5+5";
     * assert_eq!(to_postfix(s), "5 5 +");
     * ```
     *
     */
    fn to_postfix(mut infix: String) -> String {
        let mut postfix : String = "".to_string();

        // Stack to temporarily hold operators, and nr is
        // a helper variable to group digits together in their number
        let mut stack : std::vec::Vec<char> = std::vec::Vec::new();
        let mut nr : String = "".to_string();

        // If first character in infix is a minus-operator (AKA first number is a negative)
        // Add "0" to create "0-[number]"
        if infix.ch(0) == '-' {
            infix = format!("{}{}", "0", infix);
        }

        // Looping over infix string
        for mut i in 0..infix.len() {
            
            // If currently evaluated character ain't an operator, it's a digit
            if !is_operator(infix.ch(i)) {
               
                // If digit is first one in a group of digits (AKA a number)
                // put that number in nr
                while !is_operator(infix.ch(i)) && i < infix.len() {
                    nr = format!("{}{}", nr, infix.chars().nth(i).unwrap());
                    i = i + 1;
                }

                i = i - 1;

                // Append number to postfix string
                postfix = format!("{}{}", postfix, nr);
                nr = "".to_string();
            } else {
                // This block is executed when the evaluated character is an operator

                // If the stack is empty, or the evaluated operator has a higher precedence than the
                // one in the stack, push it (Needs to be appended to the postfix string later)
                if stack.is_empty() || precedence(infix.ch(i)) > precedence(*stack.last().unwrap()) {
                    stack.push(infix.ch(i));
                } else {
                    // While the stack contains a higher or equally high precedence as the
                    // evaluated character: append top of stack to postfix string
                    while precedence(*stack.last().unwrap()) >= precedence(infix.ch(i)) {
                        postfix = format!("{}{}{}", postfix, stack.pop().unwrap(), ' ');
                        
                        if stack.is_empty() {
                            break;
                        }
                    }

                    // Push evaluated operator to stack
                    stack.push(infix.ch(i));
                }

            }

        }

        // Append all the remaining operators from stack to postfix string
        while !stack.is_empty() {
            postfix = format!("{}{}", postfix, stack.pop().unwrap());
        }

        postfix
    }


    /**
     * Evaluate two numbers regarding operator
     *
     * # Arguments
     * * `x` - First number to do evaluation witch
     * * `y` - Second number to do evaluation with
     * * `operator` - Operator (+, -, *, /) to evaluate `x` and `y` with
     *
     * # Return
     * Returns the result of the evaluation
     *
     * # Example
     * ```
     * assert_eq!(evaluate(5, 2, '+'), 7);
     * assert_eq!(evaluate(2, 2, '*'), 4);
     * assert_eq!(evaluate(5, 10, '-'), -5);
     * assert_eq!(evaluate(0, -5, '-'), 5);
     * ```
     *
     */
    fn evaluate(x: f64, y: f64, operator: char) -> f64 {
        match operator {
            '+' => x + y,
            '-' => x - y,
            '*' => x * y,
            '/' => x / y,
            _ => 0.00
        } 
    }

    /**
     * Calculate the result of an infix string (`s` gets converted to postfix inside this
     * function)
     *
     *
     * # Return
     * Returns the result as a double.
     *
     * # Example
     * ```
     * assert_eq!(calculate("5+5"), 10);
     * assert_eq!(calculate("5+5*2"), 15);
     * assert_eq!(calculate("5-20/2"), -5);
     * ```
     */
    pub fn calculate(s: &String) -> f64 {
       
        // Convert to postfix
        let s = to_postfix(s.clone());

        // Stack for holding operators and nr for grouping digits who belong together as a number
        let mut stack : std::vec::Vec<f64> = std::vec::Vec::new();
        let mut nr : String = "".to_string();
        for mut i in 0..s.len() {
            if s.ch(i) == ' ' {
                continue;
            } 
            
            // If evaluated character is a digit, put it in nr
            if s.ch(i).is_digit(10) {
                // If digit is first in a group of digits (AKA a number), put that
                // whole number in nr
                
                while s.ch(i).is_digit(10) {
                    nr = format!("{}{}", nr, s.ch(i));
                    i = i + 1;
                }
                i = i - 1;

                // Pushing nr to stack
                stack.push(f64::from_str(&nr).unwrap());
                nr = "".to_string();
            } else {
                // If current evaluated character is not a digit
                // but an operator, do a calculation
                
                // Retrieve first number of calculation
                let x : f64 = stack.pop().unwrap();
                let y : f64 = stack.pop().unwrap();

                // Put evaluation result in integer and push into stack
                let result : f64 = evaluate(x, y, s.ch(i));
                stack.push(result);
            }
        }

        // Final number is in stack
        *stack.last().unwrap()
    }

}


extern crate strum;
#[macro_use]
extern crate strum_macros;


widget!(MainView<MainViewState>);

// Enumeration of all possible operators
#[derive(EnumString)]
enum Operator {
    #[strum(serialize="+")]
    Plus,
    #[strum(serialize="-")]
    Min,
    #[strum(serialize="*")]
    Times,
    #[strum(serialize="/")]
    Divide,
}


#[derive(Copy, Clone)]
enum Action {
    Char(char),

}

#[derive(AsAny, Default)]
pub struct MainViewState {
    screen: String,
    action: Option<Action>,
}

impl MainViewState {
    fn action(&mut self, action: impl Into<Option<Action>>) { //?
        self.action = action.into();
    }
}

impl State for MainViewState {
    fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
        if let Some(action) = self.action {
            match action {
                Action::Char(c) => match c {
                    '=' => {
                        let mut screen = ctx.child("screen");
                        let screen_text : &String = screen.get_mut::<String>("text");
                        let result : f64 = logic::calculate(screen_text);
                    }
                    _ => {
                        ctx.child("screen").get_mut::<String16>("text").push(c);
                    }
                }
            }

            self.action = None;
        }
    }
}

// helper to request MainViewState
fn state<'a>(id: Entity, states: &'a mut StatesContext) -> &'a mut MainViewState {
    states.get_mut(id)
}

impl Template for MainView {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        
        // Initializing Grid
        let mut grid = Grid::create(); 

        // Configuring grid (amount of rows and columns)
        grid = grid
            .columns(
                Columns::create()
                    .column("*")
                    .column("*")
                    .column("*")
                    .column("*")
                    .build()
            )
            .rows(
                Rows::create()
                    .row(50.0)
                    .row(50.0)
                    .row(50.0)
                    .row(50.0)
                    .row(50.0)
                    .row(50.0)
                    .row(50.0)
                    .build()
            );
       
        //Adding textbox holding entered numbers and operators
        grid = grid.child(
            TextBox::create()
                .text("")
                .id("screen")
                .attach(Grid::row(0))
                .attach(Grid::column(0))
                .attach(Grid::column_span(4))
                .build(ctx)
        );

        // Adding all buttons from 1-9 to the grid
        // in calculator format
        let mut counter : u8 = 9;
        for i in 1..4 {
            for j in 0..3 {
                grid = grid.child(
                    Button::create()
                        .text(counter.to_string())
                        .attach(Grid::column(2-j))
                        .attach(Grid::row(i))
                        .on_click({
                            move |states, _| -> bool {
                                state(id, states).action(Action::Char(std::char::from_digit(counter as u32, 10).unwrap()));
                                true
                            }
                        })
                        .build(ctx)
                );

                counter = counter - 1;
            }
        }

        // Adding +, -, x, /
        let operators : Vec<char> = vec!['+', '-', '*', '/', '='];
        let mut i = 1;
        for operator in operators {
                grid = grid.child(
                    Button::create()
                        .text(operator.to_string())
                        .attach(Grid::column(3))
                        .attach(Grid::row(i))
                        .on_click({
                            move |states, _| -> bool {
                                state(id, states).action(Action::Char(operator));
                                true
                            }
                        })
                        .build(ctx)
                );

                i = i + 1;
        }

        // Adding zero-button (Seperate because of special column-span)
        grid = grid.child(
            Button::create()
                .text("0")
                .attach(Grid::column(0))
                .attach(Grid::row(4))
                .attach(Grid::column_span(3))
                .on_click({
                    move |states, _| -> bool {
                        state(id, states).action(Action::Char(std::char::from_digit(0, 10).unwrap()));
                        true
                    }
                }).build(ctx)
        );

        self.name("MainView").child(
            grid.build(ctx)
        )

    }
}

fn main() {
    Application::new()
        .window(|ctx| {
            Window::create()
                .title("OrbTk")
                .position((100.0, 100.0))
                .size(212.0, 336.0)
                .child(MainView::create().build(ctx))
                .build(ctx)
        })
        .run();
}

Cargo.toml:

[package]
name = "calculator"
version = "0.1.0"
authors = ["lol"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
orbtk = { git = "https://github.com/redox-os/orbtk.git", branch = "develop" }

strum = "0.18.0"
strum_macros = "0.18.0"

I think it gets caused by one of the following lines: L86, L198, L201, L290-294.
Google didn't gave any useful hits for this error though...

The correct way to get the TextBox content as String in OrbTk is

let screen_text : String = screen.get_mut::<String16>("text").as_string();

at L292.

That solved the runtime error.

I have the problem above. Thanks for your share.

1 Like