Rust + Iced: Problem with variable borrowing

I am trying to integrate Iced into my programming. I have written all the applicable piece parts including the view function. In the view function I have a column widget that contains 3 widgets, 2 buttons and 1 text. I have a called function (get_button_widget) that returns the button widget but have run into an error. The IDE is telling me that the text I am passing in is borrowed and as a result the IDE indicates that the container element I am returning from the view function cannot return a value referencing a temporary value. I think my misunderstanding is in one of two areas. 1) How to pass a variable as a reference to a function when Iced is involved or 2) The proper return type from the button function.
Any input will be appreciated. Below is the view function, the get_button_widget function and the get_text_widget function (called from the get_button_widget function).

    fn view(&self) -> iced::Element<'_, Message> {
        let label_size: f32 = 40.0;
        let count_size: f32 = 50.0;
        let plus_sign: String = "+".to_string();
        let minus_sign: String = "-".to_string();

        let column = widget::column![
            get_button_widget(&plus_sign, label_size, Message::Increment),
            // error - plus sign is borrowed here

            widget::text(self.value.to_string()).size(count_size), 
            get_button_widget(&minus_sign, label_size, Message::Decrement),
            // error - minus sign is borrowed here

        ].align_x(Horizontal::Center);

        let mycontainer: Container<'_, Message, Theme, Renderer> =
            widget::container(column)
                .align_x(Horizontal::Center)
                .align_y(Vertical::Center)
                .width(Length::Fill)
                .height(Length::Fill)
                .style(container_style)
                ;
        mycontainer.into()
        // error - cannot return value referencing a temporary value
        // error - returns a value referencing data owned by the current function

    }

pub fn get_button_widget(label: &String, label_size: f32, on_press: Message) -> iced::widget::Button<'_, Message> {
        button(get_text_widget(label, label_size))
        .on_press(on_press) 
        .style(button_style)
        .width(80)
        .height(80)
}

pub fn get_text_widget(value: &String, size: f32) -> iced::widget::Text<'_> {
     text(value).size(size).align_x(Horizontal::Center).align_y(Vertical::Center)
}

the easiest fix is to change the signature of these functions to take String by value, this makes the widgets to own the formatted string instead of borrowing it. e.g. like this:

pub fn get_button_widget<'a>(label: String, label_size: f32, on_press: Message) -> iced::widget::Button<'a, Message>;

pub fn get_text_widget<'a>(value: String, size: f32) -> iced::widget::Text<'a>;

the lifetime parameter in widgets such as the 'a in Text<'a> must outlive the widget, specifically, it must be valid through the entire view function, typically borrowed from the application states.

Thank you for the input. Works. Two questions.

  1. In my attempted solution was get_button_widget borrowing the string but the borrow did not end before the end of the view function? Therefore, the borrow checker threw an error.
  2. In your solution, you control the strings lifetime with <'a>. Does that equate to ownership passing back to the view function when get_button_widget ends?

sort of. but it's not because of the get_button_widget(), but instead required by the type signature of the view() function.

the view() funciton has an (elided) lifetime, which is what the returned iced::Element can borrow, let's call it lifetime 'a:

fn view<'a>(&'a self) -> iced::Element<'a, Message>;

then the widgets you created in the view function must all have the same lifetime 'a: mycontainer: Container<'a, ...>, column: Column<'a, ...>, etc., and because the return value of get_button_widget() is a child element of the Column, it must also have the same lifetime 'a.

in rust, lifetimes are generic parameters! specifically, 'a is a generic lifetime of the view() function, which means it must outlive the entire body of the view() function; or you can say it represents certain code region outside it, in the caller (or caller of caller, and so on) of view(), which in this case is somewhere inside the iced framework.

the real difference between my code and your original code is the type of the parameter label, yours was label: &String, mine is label: String. the lifetime is always there, because of the return type Button<'a, ...>, but your original code doesn't need to spell it out thanks to lifetiem elision. without elision, the signature looks like this:

pub fn get_button_widget<'a>(label: &'a String, label_size: f32, on_press: Message) -> iced::widget::Button<'a, Message>;

this requires the String must outlive 'a for the returned Button<'a, ..> widget, or you can say, that the Button<'a, ...> widget must borrow the String for at least lifetime 'a.

by changing it to label: String, the String is moved into and owned by the Button now, it is no longer borrowed by it, so the lifetime 'a can be whatever for the type Button<'_>, and not tied to a local variable of String.

you can do this because the text widget internally contains a Cow instead of a regular reference, which can be either a borrowed value or an owned value.

1 Like

Great explanation. Thank you.

I should point out, you don't need a String in order to create a Text widget, what iced really uses is any type that can be converted into a text Fragment (which is just an alias for Cow<'_, str>). iced already provides the implementation for common types, see the "Implementors" section of IntoFragment:

for this particular example, since the label texts don't change, you can just use &str literals, which have the 'static lifetime, and 'static can be "shortened" to any lifetime.

// the lifetime in these signatures can be elided, I just included for illustration
pub fn get_button_widget<'a>(label: &'a str, label_size: f32, on_press: Message) -> iced::widget::Button<'a, Message>;
pub fn get_text_widget<'a>(value: &'a str, size: f32) -> iced::widget::Text<'a>;

fn new(&self) -> iced::Element<'_, Message> {
    //...
    let label_size = 40.0;
    let count_size = 50.0;
    let plus_sign = "+";
    let minus_sign = "-";
    let column = column![
        // no extra `&` before `plus_sign`, literals are of type `&str`, 
        get_button_widget(plus_sign, label_size, Message::Increment),
        // all standard integer types implement `IntoFragment` already,
        // calling `.to_string()` on `self.value` is redundent
        text(self.value).size(count_size),
        // alternatively, just use a string literal without a variable binding
        get_button_widget("-", label_size, Message::Decrement),
    ].align_x(Horizontal::Center);
    //...
}

btw, for static text widgets, using literals is the most efficient way, while String is mostly used for formatted texts that need to change over time, e.g. something like:

column![
    // string literal for static text label
    button("click me").on_press(Message::Increment),
    // `format!()` returns a `String`, it will change depending on `self.count`
    text(format!("you have clicked the button {} times", self.count)),
]
column![
    // string literal for static text label
    button("click me").on_press(Message::Increment),
    // `text!()` combines a `text` widget with the `format!` macro
    text!("you have clicked the button {} times", self.count),
]

Even better, iced has a very simple text! macro, which combines text and format! into one.

I think you are spot on recommending converting both functions to accept a string literal (&str). I have updated both functions accordingly. The only thing left to do is modify the code using your suggestion of using iced provided implementation for common types so I can pass in state data such as integers (u64) into the get_text_widget function. Thank you for your assistance. It has been very helpful.