I'm making a command line tool in Rust that prints a table to the terminal. The table will contain some links that are too long to display in full so I want to show a display text instead (some terminal like iTerm2 support this).
Here's my MWE:
use tabled::{object::Segment, Alignment, Modify, Table, Tabled};
#[derive(Debug, Tabled)]
struct MyStruct {
name: String,
#[tabled(display_with = "display_option_link")]
link: Option<Link>,
}
#[derive(Debug, Tabled)]
struct Link {
pub text: String,
pub url: String,
}
impl Link {
pub fn new(text: String, url: String) -> Self {
Self { text, url }
}
}
fn display_option_link(o: &Option<Link>) -> String {
match o {
Some(s) => format!("\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\", s.url, s.text),
None => format!(""),
}
}
fn main() {
let items = vec![
MyStruct {
name: "Google".to_string(),
link: Some(Link::new(
"click".to_string(),
"https://www.google.nl/search?q=rust+is+so+awesome".to_string(),
)),
},
MyStruct {
name: "Bing".to_string(),
link: Some(Link::new(
"click".to_string(),
"http://www.bing.com".to_string(),
)),
},
];
let table = Table::new(items)
.with(Modify::new(Segment::all()).with(Alignment::left()))
.to_string();
println!("{}", table)
}
with this in my Cargo.tml
[dependencies]
tabled = "0.7.0"
When I run this it prints
+--------+------------------------------------------------------------------+
| name | link |
+--------+------------------------------------------------------------------+
| Google | click |
+--------+------------------------------------------------------------------+
| Bing | click |
+--------+------------------------------------------------------------------+
As you can see the column width for the link columns doesn't work correctly. I suspect the string formatting breaks something in the tabled crate but I'm not sure. Is there another way I can format the link? Perhaps make it fixed width? I took the string formatting from the terminal-link crate (which I couldn't get working through the API) but I don't fully understand what it does.
I tried limiting the column width but that completely broke the table. I also tried switching to the cli-table crate but that had the same problem.
By default, the tabled crate does not take escape sequences into account and thus it thinks the link length includes all invisible characters such as its address and escape characters. To have it account for them, turn on its color feature.
Related
I have a function that does multiple operations on two vectors. The vectors consist of structs that are later converted to tuples etc....
What I would like is to change the value of a struct in vector 1 by reference, so that the value in vector 1 is equal to the value in vector 2.
When you run the program, you will better see, what I mean.
You'll get the following two output lines:
new_rows after: IndexSet { rows: [IndexRow { is_old_idx: false, hash: "1", value: [], name: "", viewentry_id: "yyy" }] }
old_rows after: IndexSet { rows: [IndexRow { is_old_idx: true, hash: "1", value: [], name: "", viewentry_id: "xxx" }] }
And what I would like is that in new_rows_after.rows.viewentry_id there is also an "xxx". But it still contains the original value "yyy".
At some point I don't seem to pass the reference correctly, but I just can't find the place.
Is there perhaps an experienced Rust expert here who can see where the error might be?
Thanks for your help.
Playground link
If I may say that, your code is pretty messy. I mean, if you want us to help you, you could at least try to make it easier for us to help. One thing that generally helps is: try to reduce the length of your example code. Maybe this can help you to come up with a minimal version of your code in the future: http://blog.pnkfx.org/blog/2019/11/18/rust-bug-minimization-patterns/
(it is actually about minimal code examples for ICE, but minimizing Rust examples is really a general thing, if you ask me).
Problem
Now, to the actual problem. I don't really understand your full example code, so what I did, I just reduce it from a whooping 194 lines down to 43 lines, just keeping, what I assume, is your actual problem.
Also, I found your variable names rather confusing. I mean, what I understand that your problem is about, is that new should have the value from old but instead new has its old value instead of the new value of old -- seriously, that is just confusing. So, I went with a simple foo and bar, and here we go:
fn main() {
let dummy_foo = vec![IndexRow {
id: "good".to_string(),
}];
let dummy_bar = vec![IndexRow {
id: "bad".to_string(),
}];
let set_foo = IndexSet { rows: dummy_foo };
let mut set_bar = IndexSet { rows: dummy_bar };
// Should copy ids from set_foo to set_bar
copy_ids(&mut set_bar, &set_foo);
// Here set_bar still contains "bad"
println!("set_bar: {:?}", set_bar);
}
#[derive(Debug)]
pub struct IndexRow {
pub id: String,
}
#[derive(Debug)]
pub struct IndexSet {
pub rows: Vec<IndexRow>,
}
/// Copy ids from `src` to `dest`
pub fn copy_ids<'a, 'b>(dest: &'a mut IndexSet, src: &'b IndexSet) {
// Create tuples each with a dest and src entry
let mut tuples: Vec<(&str, &str)> = dest
.rows
.iter()
.zip(src.rows.iter())
.map(|(d, s)| (d.id.as_str(), s.id.as_str()))
.collect();
for t in tuples.iter_mut() {
let (ref mut dest_id, src_id) = t;
// Override dest with src
*dest_id = *src_id;
}
}
playground
Now as I understand it, in this above version, the issue is just that the id in set_bar should be replaced with the id in set_foo, but instead set_bar still contains the old "bad" as it is printed at the end of main.
Solution
Assuming that this in deed the case: the problem is rather simple. You need to actually change the id, which is a String. However, in the tuples variable, you have only immutable (&) strs. Therefore, *dest_id = *src_id just replaces the one reference with another and all that is only stored/modified within tuples. The actual String is never touched, it is not even accessible as such from tuples.
So, what you need to do is: get your self access to a modifiable (&mut) String and then modify that string directly. Here you can either replace the entire string e.g. with *dest_id = src_id.to_string(), or if you want to make sure that you really have a String on the left-hand side, you can call a function on it that only exists on String and not on str like dest_id.replace_range(.., src_id).
So, this version of copy_ids does what it should do:
/// Copy ids from `src` to `dest`
pub fn copy_ids<'a, 'b>(dest: &'a mut IndexSet, src: &'b IndexSet) {
// Create tuples each with a dest and src entry
let tuples: Vec<(&mut String, &str)> = dest
.rows
.iter_mut()
.zip(src.rows.iter())
.map(|(d, s)| (&mut d.id, s.id.as_str()))
.collect();
// Override dest with src
for (dest_id, src_id) in tuples.into_iter() {
// Replace the content of the String
dest_id.replace_range(.., src_id);
}
}
full example on playground
I'd like to read attributes programatically. For example, I have a struct which has attributes attached to each field:
#[derive(Clone, Debug, PartialEq, Message)]
pub struct Person {
#[prost(string, tag="1")]
pub name: String,
/// Unique ID number for this person.
#[prost(int32, tag="2")]
pub id: i32,
#[prost(string, tag="3")]
pub email: String,
#[prost(message, repeated, tag="4")]
pub phones: Vec<person::PhoneNumber>,
}
(source)
I'd like to find the tag associated with the email field.
I expect there is some code like this to get the tag at runtime:
let tag = Person::email::prost::tag;
Since attributes are read only at compile time you need to write a procedural macro to solve such issue.
You can find information with following designators in your macro.
Field name with ident
Attribute contents with meta
After you find your field name and your meta, then you can match the stringified result with given input parameter in macro like following:
macro_rules! my_macro {
(struct $name:ident {
$(#[$field_attribute:meta] $field_name:ident: $field_type:ty,)*
}) => {
struct $name {
$(#[$field_attribute] $field_name: $field_type,)*
}
impl $name {
fn get_field_attribute(field_name_prm : &str) -> &'static str {
let fields = vec![$(stringify!($field_name,$field_attribute)),*];
let mut ret_val = "Field Not Found";
fields.iter().for_each(|field_str| {
let parts : Vec<&str> = field_str.split(' ').collect();
if parts[0] == field_name_prm{
ret_val = parts[2];
}
});
ret_val
}
}
}
}
my_macro! {
struct S {
#[serde(default)]
field1: String,
#[serde(default)]
field2: String,
}
}
Please note that it assumes that every field in the struct has an attribute. And every field declaration is ending with , including last field. But with some modification on regex you can make it available for optional attributes as well.
Here working solution in Playground
For further info about designators here is the reference
Also you can take a quick look for procedural macros here
I have a custom struct like the following:
struct MyStruct {
first_field: i32,
second_field: String,
third_field: u16,
}
Is it possible to get the number of struct fields programmatically (like, for example, via a method call field_count()):
let my_struct = MyStruct::new(10, "second_field", 4);
let field_count = my_struct.field_count(); // Expecting to get 3
For this struct:
struct MyStruct2 {
first_field: i32,
}
... the following call should return 1:
let my_struct_2 = MyStruct2::new(7);
let field_count = my_struct2.field_count(); // Expecting to get count 1
Is there any API like field_count() or is it only possible to get that via macros?
If this is achievable with macros, how should it be implemented?
Are there any possible API like field_count() or is it only possible to get that via macros?
There is no such built-in API that would allow you to get this information at runtime. Rust does not have runtime reflection (see this question for more information). But it is indeed possible via proc-macros!
Note: proc-macros are different from "macro by example" (which is declared via macro_rules!). The latter is not as powerful as proc-macros.
If this is achievable with macros, how should it be implemented?
(This is not an introduction into proc-macros; if the topic is completely new to you, first read an introduction elsewhere.)
In the proc-macro (for example a custom derive), you would somehow need to get the struct definition as TokenStream. The de-facto solution to use a TokenStream with Rust syntax is to parse it via syn:
#[proc_macro_derive(FieldCount)]
pub fn derive_field_count(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
// ...
}
The type of input is ItemStruct. As you can see, it has the field fields of the type Fields. On that field you can call iter() to get an iterator over all fields of the struct, on which in turn you could call count():
let field_count = input.fields.iter().count();
Now you have what you want.
Maybe you want to add this field_count() method to your type. You can do that via the custom derive (by using the quote crate here):
let name = &input.ident;
let output = quote! {
impl #name {
pub fn field_count() -> usize {
#field_count
}
}
};
// Return output tokenstream
TokenStream::from(output)
Then, in your application, you can write:
#[derive(FieldCount)]
struct MyStruct {
first_field: i32,
second_field: String,
third_field: u16,
}
MyStruct::field_count(); // returns 3
It's possible when the struct itself is generated by the macros - in this case you can just count tokens passed into macros, as shown here. That's what I've come up with:
macro_rules! gen {
($name:ident {$($field:ident : $t:ty),+}) => {
struct $name { $($field: $t),+ }
impl $name {
fn field_count(&self) -> usize {
gen!(#count $($field),+)
}
}
};
(#count $t1:tt, $($t:tt),+) => { 1 + gen!(#count $($t),+) };
(#count $t:tt) => { 1 };
}
Playground (with some test cases)
The downside for this approach (one - there could be more) is that it's not trivial to add an attribute to this function - for example, to #[derive(...)] something on it. Another approach would be to write the custom derive macros, but this is something that I can't speak about for now.
I'm writing a program to extract information from log files (which are in text format). The overall flow is
Read the file line-by-line into a String
Create a ParsedLine structure which borrows several string slices from that line (some using Cow)
Use the ParsedLine to write a CSV record.
This has been going very well so far, but I have run into a problem I do not understand, I think it is with lifetimes or data-flow analysis. The problem is with a small refactor I am trying to make.
I have this function which works:
fn process_line(columns: &[Column], line: String, writer: &mut Writer<File>) {
let parsed_line = ParsedLine::new(&line);
if parsed_line.is_err() {
let data = vec![""];
writer.write_record(&data).expect("Writing a CSV record should always succeed.");
return;
}
let parsed_line = parsed_line.unwrap();
// let data = output::make_output_record(&parsed_line, columns);
// The below code works. But if I try to pull it out into a separate function
// Rust will not compile it.
let mut data = Vec::new();
for column in columns {
match column.name.as_str() {
config::LOG_DATE => data.push(parsed_line.log_date),
config::LOG_LEVEL => data.push(parsed_line.log_level),
config::MESSAGE => data.push(&parsed_line.message),
_ => {
let ci_comparer = UniCase::new(column.name.as_str());
match parsed_line.kvps.get(&ci_comparer) {
Some(val) => {
let x = val.as_ref();
data.push(x);
},
None => data.push(""),
}
},
}
}
writer.write_record(&data).expect("Writing a CSV record should always succeed.");
}
But I want to pull out the bit of code that constructs data into a separate function so that I can test it more easily. Here's the function:
pub fn make_output_record<'p, 't, 'c>(parsed_line: &'p ParsedLine<'t>, columns: &'c [Column]) -> Vec<&'t str> {
let mut data = Vec::new();
for column in columns {
match column.name.as_str() {
config::LOG_DATE => data.push(parsed_line.log_date),
config::LOG_LEVEL => data.push(parsed_line.log_level),
config::MESSAGE => data.push(&parsed_line.message),
_ => {
let ci_comparer = UniCase::new(column.name.as_str());
match parsed_line.kvps.get(&ci_comparer) {
// This is the problem here. To make it explicit:
// val is a "&'t Cow<'t, str>" and x is "&'t str"
Some(val) => {
let x = val.as_ref();
data.push(x);
},
None => data.push(""),
}
},
}
}
data
}
And the error I get and do not understand is:
error[E0623]: lifetime mismatch
--> src/main.rs:201:5
|
177 | pub fn make_output_record<'p, 't, 'c>(parsed_line: &'p ParsedLine<'t>, columns: &'c [Column]) -> Vec<&'t str> {
| ------------ ------------
| |
| this parameter and the return type are declared with different lifetimes...
...
201 | data
| ^^^^ ...but data from `columns` is returned here
The compiler thinks that the returned vector contains information from Columns, but Columns is actually only used to get the name of the column, which is then used to lookup a value in the kvps HashMap (UniCase is used to make the lookup case-insensitive). If a value is found, we add the &str to data.
So I don't understand why the compiler thinks that something from Columns ends up in data, because to my mind Columns is just a bit of metadata used to drive the final contents of data, but does not in and of itself appear in data. Once the kvps lookup is done and we have the value Columns might as well not exist.
I've tried various ways of fixing this (including adding explicit lifetimes to everything, removing some lifetimes, and adding various outlives lifetime specifications) but no combination appears to be able to tell the compiler that Columns is not used in data.
For reference, here is the definition of ParsedLine:
#[derive(Debug, Default, PartialEq, Eq)]
pub struct ParsedLine<'t> {
pub line: &'t str,
pub log_date: &'t str,
pub log_level: &'t str,
pub message: Cow<'t, str>,
pub kvps: HashMap<UniCase<&'t str>, Cow<'t, str>>
}
Note that I am resisting getting rid of the Cows: I assume this would fix the problem, but the number of String allocations would probably rise by a factor of 20 and I'd like to avoid that. The current program is impressively fast!
I suspect the problem is actually with that UniCase<&'t str> and I need to give the key it's own lifetime. Not sure how though.
So my question is
Why can't I easily move this code into a new function?
How do I fix it?
I appreciate this is a rather long question. It may be easier to fiddle with the code locally. It is on Github and the error should be reproducable with:
git clone https://github.com/PhilipDaniels/log-file-processor
git checkout 80158b3
cargo build
The call for make_output_record from process_line will infer the lifetime parameter of make_output_record.
pub fn make_output_record<'p>(parsed_line: &'p ParsedLine, columns: &'p [Column]) -> Vec<&'p str> {
This means 'p is a lifetime which the owner will be alive in process_line's scope(because of the inference). According to your code parsed_line and columnslives in 'p. The 'p is the common lifetime for your return value and the arguments. That's why your code was not working because 'p, 't ,'c is not common for arguments and your return value.
I simplified your code in here, this is the working version, you can have your error back if you add other life time parameters back to make_output_record.
I'm working on a (rather ambitious) text editor, and I'm trying to implement arbitrary vertical and horizontal splits to display text buffers in, something like this:
buffer
-----------------
buffer |
-------| buffer
buffer |
I have this structure represented as a binary tree-type thing:
h
/ \
v b
/ \
b h
/ \
b b
where v is a vertical split, h is a horizontal split, and b is a buffer.
In code form, it is this:
pub enum LayoutNode {
Buf(Buffer),
Split(Box<Split>),
}
pub enum Split {
Vertical(LayoutNode, LayoutNode),
Horizontal(LayoutNode, LayoutNode)
}
pub struct Buffer {
content: String,
// more buffer-related stuff
}
All is well. My vertical split method:
impl LayoutNode {
pub fn vertical_split(layout: LayoutNode) -> LayoutNode {
LayoutNode::Split(Box::new(Split::Vertical(layout, LayoutNode::Buf(Buffer::new()))))
// Buffer::new() returns an empty Buffer
}
}
This function compiles, but is not the whole story. I have a data structure responsible for the editor's layout nodes, called Editor:
impl Editor {
pub fn new() -> Editor {
Editor {
buffers: LayoutNode::Buf(Buffer::empty()),
// more editor-related stuff
}
}
pub fn vertical_split(&mut self) {
// buffers needs to be a part of itself
self.buffers = LayoutNode::vertical_split(self.buffers);
// cannot move out of borrowed content ^
}
}
I've taken a look at mem::replace but I'm not sure that it's what I need for this case with nested data structures. The rustc --explain page for E0507 isn't very helpful in this regard.
How do I work with the borrow checker in this case? I'd rather not clone everything, since that will easily waste a lot of memory with a new copy of every file upon each split.
mem::replace is often used in situations like this to set the field to a dummy, but valid value while you're producing the new value. This is necessary to ensure that if the thread panics while producing the new value, the destructor will not free the same objects twice.
In your case, it might look something like this:
impl Editor {
pub fn new() -> Editor {
Editor {
buffers: LayoutNode::Buf(Buffer::empty()),
}
}
pub fn vertical_split(&mut self) {
// buffers needs to be a part of itself
self.buffers = LayoutNode::vertical_split(
mem::replace(&mut self.buffers, LayoutNode::Buf(Buffer::empty())));
}
}
It works like this: mem::replace receives a mutable reference to the variable or field to replace and the value to assign, and returns the old value. You get ownership of the result, so you can move it freely.