What is an idiomatic Rust way to format a value to multiple kinds of strings? - rust

I have latitude in degrees in the form of a f64 and I need to convert it into a String. At first I thought about implementing Display, like this:
struct Latitude(f64);
impl fmt::Display for Latitude {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", if self.0 > 0. { "N" } else { "S" }, self.0)
}
}
fn main() {
let lat: f64 = 45.;
println!("{}", Latitude(lat));
}
After that, I have additional requirements. I need convert to one of two representations:
N 70.152351
N 70° 09' 08"
There is also an additional flag; when it is false, I need something like:
- --.------
- --° -' -"
The most simple way to implement this will be:
fn format_lat(degree: f64, use_min_sec_variant: bool, is_valid: bool) -> String;
However, I don't see any free functions in the Rust standard library.
Maybe I should use struct Latitude(f64) and implement a to_string method? Or maybe I should implement some other trait?

You can create wrapper types with different types of formatting and return them as a method:
struct Latitude(f64);
struct MinutesSeconds(f64);
impl Latitude {
fn minutes_seconds(&self) -> MinutesSeconds {
MinutesSeconds(self.0)
}
}
impl fmt::Display for MinutesSeconds {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "latitude in a different format")
}
}
This is the same idea as Path::display.
As for the valid / invalid concept, it sounds like you really want a struct Latitude(Option<f64>) and then provide None when it is invalid.

Basically you're free to do what you want. Without more context on your goals, any method seems good enough for me.
Personally I'd do it as:
struct Latitude(f64);
impl Latitutde {
pub fn format_as_x(&self, f: &mut fmt::Formatter) -> fmt::Result<(), Error> {}
pub fn format_as_y(&self, f: &mut fmt::Formatter) -> fmt::Result<(), Error> {}
}
plus a Display trait + to_format_x() -> String convenience functions.
format_as_y are IMO best method since they handle errors, can take any formatter and don't need to allocate a String to return.
By the way, taking bool arguments is usually an anti-pattern: boolean trap.

You can impl Latitude if you need to pass arguments or impl ToString for Latitude if you don't:
use std::string::ToString;
struct Latitude(f64);
// If you need parameterisation on your conversion function
impl Latitude {
// You'd probably use a better-suited name
pub fn to_string_parameterised(&self, use_min_sec_variant: bool) -> String {
// Conversion code
format!("{} {}", self.0, use_min_sec_variant)
}
}
// If you don't need parameterisation and want to play nicely with 100% of all code elsewhere
impl ToString for Latitude {
fn to_string(&self) -> String {
// Conversion code
format!("{}", self.0)
}
}
fn main() {
let lat = Latitude(45.0);
println!("{}", lat.to_string_parameterised(false));
println!("{}", lat.to_string());
}

Related

Is there a nicer way to implement Display for structs that own collections of things with DIsplay?

I keep finding myself writing Display for structs that hold Vec of some type that implements Display. For example:
use std::fmt::Display;
struct VarTerm {
pub coeffecient: usize,
pub var_name: String,
}
impl Display for VarTerm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.coeffecient, self.var_name)
}
}
struct Function {
pub terms: Vec<VarTerm>,
}
impl Display for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let strings = self
.terms
.iter()
.map(|s| format!("{}", s))
.collect::<Vec<String>>()
.join(" + ");
write!(f, "{}", strings)
}
}
fn main() {
let my_function = Function {
terms: vec![
VarTerm {coeffecient: 2,var_name: "x".to_string(),},
VarTerm {coeffecient: 4,var_name: "y".to_string(),},
VarTerm {coeffecient: 5,var_name: "z".to_string(),},
],
};
println!("All that work to print something: {}", my_function)
}
This looks bulky and ugly to me in a bunch of places - coming from higher-level languages I'm never a fan of the .iter()/.collect() sandwiching (I kind of get why it's needed but it's annoying when 90+ percent of the time I'm just going from Vec to Vec). In this case it's also compounded by the format!() call, which I swear has to be the wrong way to do that.
I'm not sure how much of this is inherent to Rust and how much is me not knowing the right way. I want to get as close as possible to something like:
self.terms.map(toString).join(" + "), which is about what I'd expect in something like Scala.
How close can I get to there? Along the way, is there anything to be done about the aforementioned iter/collect sandwiching in general?
In an eerie coincidence, literally 2 minutes ago I looked at a few methods in the itertools crate. How about this one:
https://docs.rs/itertools/latest/itertools/trait.Itertools.html#method.join
fn join(&mut self, sep: &str) -> String
where
Self::Item: Display
Combine all iterator elements into one String, separated by sep.
Use the Display implementation of each element.
use itertools::Itertools;
assert_eq!(["a", "b", "c"].iter().join(", "), "a, b, c");
assert_eq!([1, 2, 3].iter().join(", "), "1, 2, 3");
EDIT: Additionally, whenever you ask yourself if there was a nicer way to implement a particular trait, especially when the implementation would be somewhat recursive, you should look if there's a derive macro for that trait. Turns out there is, albeit in a separate crate:
https://jeltef.github.io/derive_more/derive_more/display.html
Example:
#[derive(Display)]
#[display(fmt = "({}, {})", x, y)]
struct Point2D {
x: i32,
y: i32,
}
If you find yourself repeating this a lot you might want to move the .into_iter()/.collect() into a specific trait at the cost of generality and composability.
You can also pass ToString::to_string to map.
impl Display for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let strings = self.terms.map(ToString::to_string).join(" + ");
f.write_str(&strings)
}
}
trait VecMap<'a, T: 'a, U>: IntoIterator<Item = T> {
fn map(self, f: impl FnMut(T) -> U) -> Vec<U>;
}
impl<'a, T: 'a, U> VecMap<'a, &'a T, U> for &'a Vec<T> {
fn map(self, f: impl FnMut(&'a T) -> U) -> Vec<U> {
self.into_iter().map(f).collect()
}
}

fmt::Debug a number field with differing base (rust)

I want to the Debug format of a struct number field to show a decimal value and a hexadecimal value.
So given rust struct (rust playground)
#[derive(Default)]
pub struct S1 {
pub num1: u64,
}
fn main() {
let mut s1 = S1::default();
s1.num1 = 0xFF;
println!("{:?}", s1)
}
I would like fmt::Debug implementation (i.e. format string "{:?}") to show two different based values for field num1, i.e. to print
S1 { num1: 255 (0xFF) }
(I'm not sure how to solve the easier problem: format the numeric field as hexadecimal).
Here is what I have for fmt::Debug implementation
impl std::fmt::Debug for S1 {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("S1")
.field("num1", &self.num1)
.finish()
}
}
I do not know which part of std::fmt module would help me.
You can use format_args! to construct the formatting arguments in a low-cost way:
impl std::fmt::Debug for S1 {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("S1")
.field("num1", &format_args!("{0:?} (0x{0:X})", &self.num1))
.finish()
}
}

Is it possible to pass formatting expression?

For example, I have a very simple struct which contains only a float, but I want to implement the Display trait for this struct while also be able to set precision for the float inside of the struct...
Sorry I'm very bad at describing things, here's the sample code:
struct NewFloat(f64);
impl std::fmt::Display for NewFloat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.pad(&self.0.to_string())
}
}
fn main() {
let test: NewFloat = NewFloat(0.046);
println!("{:.2}", test); // 0.
println!("{:.2}", 0.046); // 0.05
}
I'd prefer printing test giving me same result as printing 0.046, which applies precision to floating points only and also rounds the number, how do I do that?
You can simply delegate to the existing implementation for field:
impl std::fmt::Display for NewFloat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
// or, for more explicitness:
// std::fmt::Display::fmt(&self.0, f)
}
}
Playground

Rust trait issues trait cannot be made into an object

I'm trying to write some code that will generate a random struct with a random value. I have the following trait and helper macros for the structs:
use rand::{thread_rng, Rng};
use std::fmt;
pub trait DataType {
/// generate a new instance of the type with a random value
fn random() -> Box<Self>;
/// generate a new instance of the same type with a random value
fn gen_another(&self) -> Box<Self>;
}
macro_rules! impl_data_type_for_num {
($x:ident) => {
impl DataType for $x {
fn random() -> Box<Self> {
Box::new(Self {
value: thread_rng().gen()
})
}
fn gen_another(&self) -> Box<Self> {
Self::random()
}
}
};
}
macro_rules! impl_formatting {
($x:ident, $s:expr) => {
impl fmt::Debug for $x {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, $s)
}
}
impl fmt::Display for $x {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value)
}
}
};
}
Then I use the macros to implement the needed traits on a bunch of structs (heres a few for example):
pub struct VirtBool {
value: bool
}
impl_data_type_for_num!(VirtBool);
impl_formatting!(VirtBool, "bool");
pub struct VirtU8 {
value: u8
}
impl_data_type_for_num!(VirtU8);
impl_formatting!(VirtU8, "u8");
pub struct VirtU16 {
value: u16
}
impl_data_type_for_num!(VirtU16);
impl_formatting!(VirtU16, "u16");
So far it all works fine, but then an issue arises when I try to implement the same traits on a struct with unsized fields:
pub struct VirtArray {
_type: Box<dyn DataType>,
value: Vec<Box<dyn DataType>>
}
impl DataType for VirtArray {
fn random() -> Box<Self> {
let t = random_var();
let s = thread_rng().gen_range(0, 10);
Box::new(Self {
_type: *t,
value: (0..s).map(|_| t.gen_another()).collect()
})
}
fn gen_another(&self) -> Box<Self> {
Box::new(Self {
_type: self._type,
value: self.value.iter().map(|t| t.gen_another()).collect::<Vec<Box<dyn DataType>>>()
})
}
}
impl fmt::Debug for VirtArray {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{:?}; {}]", self._type, self.value.len())
}
}
impl fmt::Display for VirtArray {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = self.value.iter().map(|v| format!("{}, ",v)).collect::<String>();
s.truncate(s.len().checked_sub(2).unwrap_or(s.len()));
write!(f, "[{}]", s)
}
}
This throws the error the trait 'DataType' cannot be made into an object on several occasions. I then have a function to create a random struct but that also throws the same error:
/// generate a random type with random value
fn random_var() -> Box<dyn DataType> {
match thread_rng().gen_range(0,4) {
0 => Box::new(VirtBool::random()),
1 => Box::new(VirtU8::random()),
2 => Box::new(VirtU16::random()),
3 => Box::new(VirtArray::random()),
_ => panic!("invalid")
}
}
I was originally using enums to do all of this but I'm trying to switch it over to structs and traits to help with scaling/use-ability. Does anyone have any idea how to fix the code above? I've been at a roadblock here for quite a while now.
Also, I'm aware I could use type_name_of_val to print the types, but I'm trying to keep from using unstable/nightly features.
DataType cannot be made into a trait object because it uses Self and because it has a static method.
I realize it might seem like returning Box<Self> may be reasonable to call on a dyn DataType, since if you call it on dyn DataType you want a Box<dyn DataType>, but Rust doesn't try to modify methods for you to turn methods that return e.g. Box<VirtArray> into ones that return Box<dyn DataType> if they are called on a dyn DataType value. You can work around this by having the methods return Box<dyn DataType> instead.
Static methods are not allowed for trait objects because there is no implementation for the trait object type. Remember, dyn Foo implements Foo. What would (dyn DataType)::random() be? (You can use a where clause, as in the example above, to make sure dyn DataType isn't expected to have this method in a way that can be detected ahead of time; this means you can't use it on your dyn DataType objects, but it sounds like you might not want to.)

How to implement Display on a trait object where the types already implement Display

I have some code which returns a trait object of type MyTrait so that it can return one of several different structs. I would like to implement the Display trait for the trait object so that I can print the object, with the details delegated to the various structs as they each need their own custom formatters.
I can achieve this by including a formatting method as part of the MyTrait definition, and then implementing Display for MyTrait and delegating - like this:
trait MyTrait {
fn is_even(&self) -> bool;
fn my_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
}
impl fmt::Display for MyTrait {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.my_fmt(f)
}
}
However, I already have the Display trait implemented for each of the structs which implement MyTrait. This means I end up with two methods for each struct which do the same thing - the fmt() method to satisfy the Display trait directly on the struct, and the my_fmt() method which is called by the code above. This seems clumsy and repetitive. Is there a simpler way to do it?
Here's a complete example program which illustrates the point. It's a little longer than I would have liked (it's based on the answer to my previous question Calling functions which return different types with shared trait and pass to other functions), but I couldn't think of a simpler way to illustrate the point. Of course, in this toy example the structs and the fmt functions are very simple; in my real application they are more complex.
use std::fmt;
trait MyTrait {
fn is_even(&self) -> bool;
fn my_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
}
struct First {
v: u8,
}
struct Second {
v: Vec<u8>,
}
impl MyTrait for First {
fn is_even(&self) -> bool {
self.v % 2 == 0
}
fn my_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.v)
}
}
impl MyTrait for Second {
fn is_even(&self) -> bool {
self.v[0] % 2 == 0
}
fn my_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.v[0])
}
}
fn make1() -> First {
First { v: 5 }
}
fn make2() -> Second {
Second { v: vec![2, 3, 5] }
}
// Implement Display for the structs and for MyTrait
impl fmt::Display for First {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.v)
}
}
impl fmt::Display for Second {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.v[0])
}
}
impl fmt::Display for MyTrait {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.my_fmt(f)
}
}
fn requires_mytrait<T: MyTrait + ?Sized>(v: &&T) {
println!("{:?}", v.is_even());
}
fn main() {
for i in 0..2 {
let v1;
let v2;
let v = match i {
0 => {
v1 = make1();
println!("> {}", v1); // Demonstrate that Display
// is implemented directly
// on the type.
&v1 as &MyTrait
}
_ => {
v2 = make2();
println!("> {}", v2); // Demonstrate that Display
// is implemented directly
// on the type.
&v2 as &MyTrait
}
};
requires_mytrait(&v);
println!("{}", v); // Here I print the trait object
}
}
Can anyone suggest a simpler, cleaner way to do this?
You can make Display a supertrait of MyTrait.
trait MyTrait: fmt::Display {
fn is_even(&self) -> bool;
}
This will make trait objects of MyTrait be Display. This only works if you expect all implementors of MyTrait to implement Display, but that was also the case in your previous solution.

Resources