Reputation: 15967
I'm learning rust (coming from a haskell background). And I am struggling to use polymorphic types - where in haskell I would use type holes/GHC's type inference to figure out what the return type of a function is I am stuck in rust, to specialise a generic parameter.
Say I have a type describing a pomodoro-session (I store this data in a sqllite-database) and I want to display this data in a tui-table
use tui::widgets::Row;
pub struct Pom {
pub id: i32,
pub duration: i64,
pub ticket_id: Option<String>,
pub note: Option<String>,
}
impl Pom {
pub fn as_row(self) -> ? {
let ret : [String; 4] = [
format!("{}", self.id),
format!("{}", self.duration),
self.ticket_id.unwrap_or(String::new()),
self.note.unwrap_or(String::new()),
];
Row::Data(ret.into_iter())
}
}
The compiler is quite helpful, when I substitute ?
with i32
(as suggested in (How do I print the type of a variable in Rust?), it tells me it expects something of type Row
.
I know that Row
is an enum with the following definition:
pub enum Row<D, I> where
D: Iterator<Item = I>,
I: Display, {
Data(D),
StyledData(D, Style),
}
I've tried to insert incorrect types into the specialised type signature
pub fn as_row(self) -> Row<?,?> {
i.e. substitute the ?
with i32
again the compiler tells me that i32
is not an iterator, but then I don't know how to further specialise.
To make investigation easier here:
https://github.com/epsilonhalbe/pomodorust/tree/4f8c4929201ed90ddaef8af8d21076dbde78782b is a link to the project on github that can be cloned and built. The errors I have posted here can be reproduced by modyfing src/database.rs
to make compilation fail
If there was something equivalent to _
in haskell that could tell me what type the rust compiler is inferencing, then I could go pick apart the examples in the documentation
let row_style = Style::default().fg(Color::White);
Table::new(
["Col1", "Col2", "Col3"].into_iter(),
vec![
Row::StyledData(["Row11", "Row12", "Row13"].into_iter(), row_style),
Row::StyledData(["Row21", "Row22", "Row23"].into_iter(), row_style),
Row::StyledData(["Row31", "Row32", "Row33"].into_iter(), row_style),
Row::Data(["Row41", "Row42", "Row43"].into_iter())
].into_iter()
)
.block(Block::default().title("Table"))
.header_style(Style::default().fg(Color::Yellow))
.widths(&[Constraint::Length(5), Constraint::Length(5), Constraint::Length(10)])
.style(Style::default().fg(Color::White))
.column_spacing(1);
Upvotes: 3
Views: 113
Reputation: 32189
@kopecs is right. Row<std::slice::Iter<'a, String>, &'a String>
would be the correct return type but as they explained, it would cause lifetime issues since it would be returning references to a local variable.
tui::widgets::Row
requires its type D
to be an Iterator
. The issue with using an array and into_iter
on the array is that IntoIterator
is only "implemented for &[T; N] and &mut [T; N]" i.e. references. This is best explained by this Reddit thread
any such implementation needing to move elements out of an array, which leaves holes in the array, ... can cause Bad Things to happen if a panic happens during iteration
...
I believe Vec gets around this by leaking memory in the case of a panic
Anyways, one way to fix this is to just use Vec
instead of arrays. Then, your iterator can own the String instead of only holding a reference to the String in the original array. The fixed method looks as follows:
pub fn as_row(self) -> Row<impl Iterator<Item=String>, String> { // <-- correct return type
let ret = vec![ // <-- use a Vec
format!("{}", self.id),
format!("{}", self.duration),
self.ticket_id.unwrap_or(String::new()),
self.note.unwrap_or(String::new()),
];
Row::Data(ret.into_iter())
}
Upvotes: 4