Watching a video tutorial about using iced-rs, I found an oddity in the usage of enum variants in place of FnOnce. At least, that's how I understand it. I suspect there is additional behaviour provided by the iced-rs, something like an extension trait, that I haven't discovered yet. Because I tried to reproduce such behavior in the Rust Playground and I received an (expected) compile-time error like "wrong type - FnOnce expected".
So, I have the following code:
#[derive(Debug, Clone)]
enum Message {
Edit(text_editor::Action),
FileOpened(Result<Arc<String>, ErrorKind>),
}
// skip
Command::perform(
load_file(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR"))),
Message::FileOpened,
);
where the signature of Command::perform is:
pub fn perform<A>(
future: impl Future<Output = T> + 'static + MaybeSend,
f: impl FnOnce(T) -> A + 'static + MaybeSend,
) -> Command<A>
As I understand it, the second parameter must be a type of FnOnce. But why am I able to pass the Message::FileOpened variant instead of it? And why am I able to use Message::FileOpened without parameters, despite this variant having associated data (a tuple)?
Full source code
Show
use iced::widget::{column, container, horizontal_space, row, text, text_editor};
use iced::{executor, Application, Command, Length, Settings, Theme};
use std::io::ErrorKind;
use std::path::Path;
use std::sync::Arc;
fn main() -> iced::Result {
Editor::run(Settings::default())
}
struct Editor {
content: text_editor::Content,
}
#[derive(Debug, Clone)]
enum Message {
Edit(text_editor::Action),
FileOpened(Result<Arc<String>, ErrorKind>),
}
impl Application for Editor {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
(
Self {
content: text_editor::Content::new(),
},
Command::perform(
load_file(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR"))),
Message::FileOpened,
),
)
}
fn title(&self) -> String {
String::from("Iced Editor")
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::Edit(action) => self.content.edit(action),
Message::FileOpened(result) => {
if let Ok(content) = result {
self.content = text_editor::Content::with(&content)
}
}
};
Command::none()
}
fn view(&self) -> iced::Element<'_, Self::Message> {
let input = text_editor(&self.content).on_edit(Message::Edit);
let position = {
let (line, column) = self.content.cursor_position();
text(format!("{}:{}", line, column))
};
let status_bar = row![horizontal_space(Length::Fill), position];
container(column![input, status_bar].spacing(10))
.padding(10)
.into()
}
fn theme(&self) -> Theme {
Theme::Dark
}
}
async fn load_file(path: impl AsRef<Path>) -> Result<Arc<String>, ErrorKind> {
tokio::fs::read_to_string(path)
.await
.map(Arc::new)
.map_err(|error| error.kind())
}