From 6c79af8213819b44c41df230394798edefbc4a3b Mon Sep 17 00:00:00 2001 From: Knyffen Date: Wed, 14 Aug 2024 17:39:47 +0200 Subject: [PATCH] Add calorie intake plot --- src/main.rs | 186 ++++++++------------------------- src/math.rs | 3 + src/math/calculations.rs | 142 +++++++++++++++++++++++++ src/my_structs/nutrition.rs | 50 +++++++-- src/my_structs/purchases.rs | 23 +++- src/plotting.rs | 11 +- src/plotting/bitmap_plotter.rs | 91 ++++++++++++++++ src/plotting/plotter.rs | 40 +++---- src/plotting/svg_plotter.rs | 6 +- 9 files changed, 373 insertions(+), 179 deletions(-) create mode 100644 src/math.rs create mode 100644 src/math/calculations.rs create mode 100644 src/plotting/bitmap_plotter.rs diff --git a/src/main.rs b/src/main.rs index bd519af..bca71a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,21 +2,26 @@ use axum::{ extract::State, - http::{header, StatusCode, Uri}, + http::{ + // header, + StatusCode, + Uri, + }, response::{Html, IntoResponse}, routing::get, Router, }; -use image::ImageFormat; -use mysql::prelude::*; +// use image::ImageFormat; +// use mysql::prelude::*; use mysql::*; -use plotters::prelude::*; -use std::io::{BufWriter, Cursor}; +// use plotters::prelude::*; +// use std::io::{BufWriter, Cursor}; use std::sync::Arc; +mod math; mod my_structs; mod plotting; -use crate::my_structs::MyError; -use crate::plotting::Plotter; +// use crate::my_structs::MyError; +// use crate::plotting::Plotter; use std::{thread, time}; #[derive(Clone)] @@ -49,7 +54,8 @@ async fn main() { .route("/html_plotter", get(html_plotter)) .route("/image.png", get(png_image)) .route("/image.svg", get(svg_image)) - .route("/image2.svg", get(svg_image2)) + .route("/calorie_intake.svg", get(svg_calorie_intake)) + .route("/calorie_intake", get(calorie_intake)) .with_state(shared_state) .fallback(not_found); @@ -59,43 +65,19 @@ async fn main() { } async fn not_found(uri: Uri) -> (StatusCode, String) { - (StatusCode::NOT_FOUND, format!("No route for {uri}")) + (StatusCode::NOT_FOUND, format!("404 not found: {uri}")) } -async fn get_nutrition_rows(sql_pool: Pool) -> Result> { - let mut conn = sql_pool - .get_conn() - .expect("Cannot establish database connection"); - - let (nutrition_query, nutrition_closure) = my_structs::Nutrition::query_map_helper(); - let nutrition_val: Vec = conn - .query_map(nutrition_query, nutrition_closure) - .expect("Data in database doesn't match the Nutrition class"); - - Ok(nutrition_val) -} - -async fn get_purchases_rows(sql_pool: Pool) -> Result> { - let mut conn = sql_pool - .get_conn() - .expect("Cannot establish database connection"); - - let (purchases_query, purchases_closure) = my_structs::Purchase::query_map_helper(); - let purchases_val: Vec = conn - .query_map(purchases_query, purchases_closure) - .expect("Data in database doesn't match the Purchase class"); - - Ok(purchases_val) -} - -async fn get_rows(State(state): State>) -> Result { - let nutrition_val_promise = get_nutrition_rows(state.sql_pool.clone()); - let purchases_val_promise = get_purchases_rows(state.sql_pool.clone()); +async fn get_rows(State(state): State>) -> Result { + let nutrition_val_promise = + my_structs::Nutrition::get_nutrition_hashmap(state.sql_pool.clone()); + let purchases_val_promise = my_structs::Purchase::get_purchase_rows(state.sql_pool.clone()); let nutrition_val = nutrition_val_promise.await?; let purchases_val = purchases_val_promise.await?; println!("Serving: get_rows"); + // Ok("See print logs instead".to_string()) Ok(format!("{:#?} {:#?}", nutrition_val, purchases_val)) } @@ -105,122 +87,42 @@ async fn html_demo(State(_state): State>) -> Html { Html("

This is HTML

".to_string()) } -fn format_bitmap_as_png(bitmap: Vec, width: u32, height: u32) -> Vec { - let image = - image::ImageBuffer::, Vec>::from_raw(width, height, bitmap).unwrap(); - let mut buffer = BufWriter::new(Cursor::new(Vec::new())); - image.write_to(&mut buffer, ImageFormat::Png).unwrap(); - let bytes: Vec = buffer.into_inner().unwrap().into_inner(); - bytes +async fn svg_image(State(_state): State>) -> impl IntoResponse { + println!("Serving: svg_image"); + let fun = plotting::SVGPlotter::examplefun(); + plotting::SVGPlotter::plot(640, 480, fun) } -fn plot_stuff_bitmap(image_buffer: &mut [u8], width: u32, height: u32) { - let root = BitMapBackend::with_buffer(image_buffer, (width, height)).into_drawing_area(); - root.fill(&WHITE).unwrap(); - let mut chart = ChartBuilder::on(&root) - .caption("y=x^2", ("sans-serif", 50).into_font()) - .margin(5) - .x_label_area_size(30) - .y_label_area_size(30) - .build_cartesian_2d(-1f32..1f32, -0.1f32..1f32) - .unwrap(); +async fn svg_calorie_intake( + State(state): State>, +) -> Result { + println!("Serving: svg_calorie_intake"); - chart.configure_mesh().draw().unwrap(); + let nutrition_val_promise = + my_structs::Nutrition::get_nutrition_hashmap(state.sql_pool.clone()); + let purchases_val_promise = my_structs::Purchase::get_purchase_rows(state.sql_pool.clone()); - chart - .draw_series(LineSeries::new( - (-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)), - &RED, - )) - .unwrap() - .label("y = x^2") - .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED)); + let nutrition_val = nutrition_val_promise.await?; + let purchases_val = purchases_val_promise.await?; - chart - .configure_series_labels() - .background_style(WHITE.mix(0.8)) - .border_style(BLACK) - .draw() - .unwrap(); - - root.present().unwrap(); + let plotfun = math::plot_calories_per_day(nutrition_val, purchases_val); + Ok(plotting::SVGPlotter::plot(640, 480, plotfun)) } async fn png_image(State(_state): State>) -> impl IntoResponse { println!("Serving: png_image"); - - let width: u32 = 640; - let height: u32 = 480; - let mut image_buffer = vec![0; (width as usize) * (height as usize) * 3]; - plot_stuff_bitmap(image_buffer.as_mut_slice(), width, height); - - let image = format_bitmap_as_png(image_buffer, width, height); - - let headers = [ - (header::CONTENT_TYPE, "image/png"), - (header::CONTENT_DISPOSITION, "inline"), - ]; - (headers, image).into_response() -} - -fn plot_stuff_svg(image_string: &mut String, width: u32, height: u32) { - let root = SVGBackend::with_string(image_string, (width, height)).into_drawing_area(); - root.fill(&WHITE).unwrap(); - let mut chart = ChartBuilder::on(&root) - .caption("y=x^2", ("sans-serif", 50).into_font()) - .margin(5) - .x_label_area_size(30) - .y_label_area_size(30) - .build_cartesian_2d(-1f32..1f32, -0.1f32..1f32) - .unwrap(); - - chart.configure_mesh().draw().unwrap(); - - chart - .draw_series(LineSeries::new( - (-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)), - &RED, - )) - .unwrap() - .label("y = x^2") - .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED)); - - chart - .configure_series_labels() - .background_style(WHITE.mix(0.8)) - .border_style(BLACK) - .draw() - .unwrap(); - - root.present().unwrap(); -} - -async fn svg_image(State(_state): State>) -> impl IntoResponse { - println!("Serving: svg_image"); - - let width: u32 = 640; - let height: u32 = 480; - let mut image_string = String::new(); - plot_stuff_svg(&mut image_string, width, height); - - let headers = [ - (header::CONTENT_TYPE, "image/svg+xml"), - (header::CONTENT_DISPOSITION, "inline"), - ]; - (headers, image_string).into_response() -} - -async fn svg_image2(State(_state): State>) -> impl IntoResponse { - println!("Serving: svg_image2"); - let fun = plotting::SVGPlotter::examplefun(); - plotting::SVGPlotter::plot(640, 480, fun) + let fun = plotting::BitMapPlotter::examplefun(); + plotting::BitMapPlotter::plot(640, 480, fun, plotting::BitMapOutputFormat::PNG) } async fn html_plotter(State(_state): State>) -> Html { println!("Serving: html_plotter"); - Html( - "

This is HTML

" - .to_string(), - ) + Html("".to_string()) +} + +async fn calorie_intake(State(_state): State>) -> Html { + println!("Serving: calorie_intake"); + + Html("".to_string()) } diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 0000000..64d6a85 --- /dev/null +++ b/src/math.rs @@ -0,0 +1,3 @@ +// pub use self::calculations::calculate_calories_per_day; +pub use self::calculations::plot_calories_per_day; +mod calculations; diff --git a/src/math/calculations.rs b/src/math/calculations.rs new file mode 100644 index 0000000..7ac049a --- /dev/null +++ b/src/math/calculations.rs @@ -0,0 +1,142 @@ +use crate::my_structs; +use crate::plotting; +// use chrono::TimeZone; +use chrono::{DateTime, NaiveDate, Utc}; +use plotters::prelude::*; +use std::collections::HashMap; +use std::time::{SystemTime, UNIX_EPOCH}; +use time::Date; +use time::*; + +const MEALS_PER_DAY: f32 = 2.3; +const SMOOTHING_DAYS: usize = 14; + +fn time_date_to_chrono_naive_date(d: Date) -> chrono::NaiveDate { + chrono::NaiveDate::from_ymd_opt(d.year(), d.month() as u32, d.day() as u32).unwrap() +} + +fn calculate_calories_per_day( + nutrition_map: HashMap, + purchases: Vec, +) -> (Date, Vec) { + if purchases.is_empty() { + return (Date::MIN, vec![]); // TODO: vec![] or vec![0.]? + } + + // let mindate = purchases.into_iter(); //.map(|x| x.datetime.date()).min(); + let mindate: Date = purchases.iter().map(|x| x.datetime.date()).min().unwrap(); + let maxdate: Date = purchases.iter().map(|x| x.datetime.date()).max().unwrap(); + let date_range: usize = ((maxdate - mindate).whole_days() + 1).try_into().unwrap(); + let mut calorie_purchases = vec![0.; date_range]; + let mut mystery_meals: Vec = vec![0; date_range]; + purchases.iter().for_each(|p| { + let dateindex: usize = (p.datetime.date() - mindate) + .whole_days() + .try_into() + .unwrap(); + + let nutrition = &nutrition_map[&p.nutrition_id]; + if nutrition.is_valid() { + let calories = nutrition_map[&p.nutrition_id].total_kcal() * p.amount as f32; + calorie_purchases[dateindex] += calories; + } else { + mystery_meals[dateindex] += 1; + } + }); + + let mut calorie_days = vec![0.; date_range + SMOOTHING_DAYS]; + calorie_purchases + .iter() + .enumerate() + .for_each(|(i, calories)| { + let calories_smoothed = calories / SMOOTHING_DAYS as f32; + + for item in &mut calorie_days[i..i + SMOOTHING_DAYS] { + *item += calories_smoothed; + } + }); + mystery_meals.iter().enumerate().for_each(|(i, amount)| { + let calories = calorie_days[i] / MEALS_PER_DAY * *amount as f32; + let calories_smoothed = calories / SMOOTHING_DAYS as f32; + + for item in &mut calorie_days[i..i + SMOOTHING_DAYS] { + *item += calories_smoothed; + } + }); + + let today_chrono = DateTime::::from(SystemTime::now()).date_naive(); + let mindate_chrono = time_date_to_chrono_naive_date(mindate); + let days_since_mindate = (today_chrono - mindate_chrono).num_days() + 1; + calorie_days.resize(days_since_mindate as usize, 0.); + + println!("{:?}", calorie_purchases); + println!("{:?}", mystery_meals); + println!("{:?}", calorie_days); + (mindate, calorie_days) +} + +pub fn plot_calories_per_day( + nutrition_map: HashMap, + purchases: Vec, +) -> plotting::SVGPlotFun { + let (mindate, calorie_days) = calculate_calories_per_day(nutrition_map, purchases); + + let maxdate = mindate + .checked_add(Duration::days(calorie_days.len().try_into().unwrap())) + .unwrap(); + + let mindate_chrono = time_date_to_chrono_naive_date(mindate); + let maxdate_chrono = time_date_to_chrono_naive_date(maxdate); + + let max_calories = calorie_days + .clone() + .into_iter() + .reduce(|a, b| a.max(b)) + .unwrap(); + + let plotfun = Box::new(move |root: plotting::SVGPlotRoot| { + root.fill(&WHITE).unwrap(); + let mut chart = ChartBuilder::on(&root) + .caption("Calorie intake", ("sans-serif", 50).into_font()) + // .margin(20) + .x_label_area_size(30) + .y_label_area_size(50) + .build_cartesian_2d(mindate_chrono..maxdate_chrono, 0f32..max_calories + 200.) + .unwrap(); + + chart + .configure_mesh() + .y_desc("kcal") + .y_max_light_lines(4) + .draw() + .unwrap(); + + chart + .draw_series(LineSeries::new( + calorie_days.iter().enumerate().map(|(i, cal)| { + ( + mindate_chrono + .checked_add_days(chrono::Days::new(i.try_into().unwrap())) + .unwrap(), + *cal, + ) + }), + RED.stroke_width(3), + )) + .unwrap() + .label("kcal") + .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED)); + + // Show labels + // chart + // .configure_series_labels() + // .background_style(WHITE.mix(0.8)) + // .border_style(BLACK) + // .position(SeriesLabelPosition::UpperRight) + // .draw() + // .unwrap(); + + root.present().unwrap(); + }); + plotfun +} diff --git a/src/my_structs/nutrition.rs b/src/my_structs/nutrition.rs index 69c555f..282d076 100644 --- a/src/my_structs/nutrition.rs +++ b/src/my_structs/nutrition.rs @@ -1,15 +1,18 @@ +use mysql::prelude::*; +use mysql::*; use mysql_common::frunk::{hlist_pat, HList}; +use std::collections::HashMap; use struct_field_names_as_array::FieldNamesAsArray; #[allow(non_snake_case)] #[derive(Debug, PartialEq, FieldNamesAsArray)] pub struct Nutrition { - id: i32, + id: u32, name: String, manufacturer: String, barcode: String, - amount: i32, - divisor: i32, + amount: u32, + divisor: u32, kJ: f32, kcal: f32, saturated_fat: f32, @@ -25,12 +28,12 @@ pub struct Nutrition { } type RowType = HList!( - i32, + u32, Option, Option, Option, - Option, - Option, + Option, + Option, Option, Option, Option, @@ -54,7 +57,7 @@ impl Nutrition { .collect() } - pub fn query_map_helper() -> (String, impl Fn(RowType) -> Nutrition) { + fn query_map_helper() -> (String, impl Fn(RowType) -> Nutrition) { let sql_query = format!("SELECT {} from nutrition", Self::get_sql_fields()); let construction_closure = |row: RowType| { @@ -102,4 +105,37 @@ impl Nutrition { (sql_query, construction_closure) } + + fn get_nutrition_rows(sql_pool: Pool) -> Result> { + let mut conn = sql_pool + .get_conn() + .expect("Cannot establish database connection"); + + let (nutrition_query, nutrition_closure) = Self::query_map_helper(); + let nutrition_val: Vec = conn + .query_map(nutrition_query, nutrition_closure) + .expect("Data in database doesn't match the Nutrition class"); + + Ok(nutrition_val) + } + + pub async fn get_nutrition_hashmap(sql_pool: Pool) -> Result> { + let nutrition_vec = Self::get_nutrition_rows(sql_pool)?; + let nutrition_hashmap: HashMap = + nutrition_vec.into_iter().map(|n| (n.id, n)).collect(); + Ok(nutrition_hashmap) + } + + pub fn total_kcal(&self) -> f32 { + match self.divisor { + 0 => 0., + _ => self.kcal * self.amount as f32 / self.divisor as f32, + } + } + + pub fn is_valid(&self) -> bool { + // Comparing self.kcal with 0. should be okay, since it is initialized to 0. and if it is + // changed in any way, this function should return true + self.amount != 0 && self.divisor != 0 && self.kcal != 0. + } } diff --git a/src/my_structs/purchases.rs b/src/my_structs/purchases.rs index 66dcae6..9abc952 100644 --- a/src/my_structs/purchases.rs +++ b/src/my_structs/purchases.rs @@ -1,3 +1,5 @@ +use mysql::prelude::*; +use mysql::*; use mysql_common::frunk::{hlist_pat, HList}; use struct_field_names_as_array::FieldNamesAsArray; use time::PrimitiveDateTime; @@ -6,9 +8,9 @@ use time::PrimitiveDateTime; #[derive(Debug, PartialEq, FieldNamesAsArray)] pub struct Purchase { id: u32, - nutrition_id: u32, - amount: u32, - datetime: PrimitiveDateTime, + pub nutrition_id: u32, + pub amount: u32, + pub datetime: PrimitiveDateTime, } type RowType = HList!(u32, u32, u32, PrimitiveDateTime); @@ -22,7 +24,7 @@ impl Purchase { .collect() } - pub fn query_map_helper() -> (String, impl Fn(RowType) -> Purchase) { + fn query_map_helper() -> (String, impl Fn(RowType) -> Purchase) { let sql_query = format!("SELECT {} from purchases", Self::get_sql_fields()); println!("{}", sql_query); @@ -38,4 +40,17 @@ impl Purchase { (sql_query, construction_closure) } + + pub async fn get_purchase_rows(sql_pool: Pool) -> Result> { + let mut conn = sql_pool + .get_conn() + .expect("Cannot establish database connection"); + + let (purchases_query, purchases_closure) = Self::query_map_helper(); + let purchases_val: Vec = conn + .query_map(purchases_query, purchases_closure) + .expect("Data in database doesn't match the Purchase class"); + + Ok(purchases_val) + } } diff --git a/src/plotting.rs b/src/plotting.rs index 46137c3..f2552d1 100644 --- a/src/plotting.rs +++ b/src/plotting.rs @@ -1,4 +1,11 @@ -pub use self::plotter::Plotter; +// pub use self::plotter::Plotter; +pub use self::bitmap_plotter::BitMapOutputFormat; +pub use self::bitmap_plotter::BitMapPlotFun; +pub use self::bitmap_plotter::BitMapPlotRoot; +pub use self::bitmap_plotter::BitMapPlotter; +pub use self::svg_plotter::SVGPlotFun; +pub use self::svg_plotter::SVGPlotRoot; pub use self::svg_plotter::SVGPlotter; -mod plotter; +// mod plotter; +mod bitmap_plotter; mod svg_plotter; diff --git a/src/plotting/bitmap_plotter.rs b/src/plotting/bitmap_plotter.rs new file mode 100644 index 0000000..9ba95c8 --- /dev/null +++ b/src/plotting/bitmap_plotter.rs @@ -0,0 +1,91 @@ +// use super::plotter::Plotter; +use axum::http::header; +use axum::response::IntoResponse; +use image::ImageFormat; +use plotters::prelude::*; +use std::io::{BufWriter, Cursor}; + +#[allow(clippy::upper_case_acronyms)] +pub enum BitMapOutputFormat { + PNG, +} + +pub struct BitMapPlotter {} +pub type BitMapPlotRoot<'a> = + plotters::drawing::DrawingArea, plotters::coord::Shift>; +pub type BitMapPlotFun = Box; + +impl BitMapPlotter { + pub fn examplefun() -> BitMapPlotFun { + Box::new(|root: BitMapPlotRoot| { + root.fill(&WHITE).unwrap(); + let mut chart = ChartBuilder::on(&root) + .caption("y=x^2", ("sans-serif", 50).into_font()) + .margin(5) + .x_label_area_size(30) + .y_label_area_size(30) + .build_cartesian_2d(-1f32..1f32, -0.1f32..1f32) + .unwrap(); + + chart.configure_mesh().draw().unwrap(); + + chart + .draw_series(LineSeries::new( + (-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)), + &RED, + )) + .unwrap() + .label("y = x^2") + .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED)); + + chart + .configure_series_labels() + .background_style(WHITE.mix(0.8)) + .border_style(BLACK) + .draw() + .unwrap(); + + root.present().unwrap(); + }) + } + + pub fn format_bitmap_as_png(bitmap: Vec, width: u32, height: u32) -> Vec { + let image = + image::ImageBuffer::, Vec>::from_raw(width, height, bitmap).unwrap(); + let mut buffer = BufWriter::new(Cursor::new(Vec::new())); + image.write_to(&mut buffer, ImageFormat::Png).unwrap(); + let bytes: Vec = buffer.into_inner().unwrap().into_inner(); + bytes + } + + pub fn plot( + width: u32, + height: u32, + plotting_fun: BitMapPlotFun, + format: BitMapOutputFormat, + ) -> impl IntoResponse { + let mut image_buffer = vec![0; (width as usize) * (height as usize) * 3]; + + { + let root = + // BitMapBackend::with_buffer(&mut image_string, (width, height)).into_drawing_area(); + BitMapBackend::with_buffer(image_buffer.as_mut_slice(), (width, height)).into_drawing_area(); + plotting_fun(root); + } + + let image = match format { + BitMapOutputFormat::PNG => Self::format_bitmap_as_png(image_buffer, width, height), + }; + + let headers = [ + ( + header::CONTENT_TYPE, + match format { + BitMapOutputFormat::PNG => "image/png", + }, + ), + (header::CONTENT_DISPOSITION, "inline"), + ]; + (headers, image).into_response() + } +} diff --git a/src/plotting/plotter.rs b/src/plotting/plotter.rs index fb1baa8..9fccc6f 100644 --- a/src/plotting/plotter.rs +++ b/src/plotting/plotter.rs @@ -1,21 +1,21 @@ -use axum::response::IntoResponse; -use plotters::prelude::*; +//use axum::response::IntoResponse; +//use plotters::prelude::*; -pub trait Plotter { - //fn plot(width: u32, height: u32, plotting_fun: &dyn Fn(IntoDrawingArea)) -> impl IntoResponse; - fn plot( - width: u32, - height: u32, - //plotting_fun: &dyn Fn(Box), - plotting_fun: Box< - dyn Fn( - plotters::drawing::DrawingArea< - plotters::backend::SVGBackend, - plotters::coord::Shift, - >, - ), - >, - ) -> impl IntoResponse; - //fn plot(width: u32, height: u32, plotting_fun: &dyn Fn()) -> impl IntoResponse; - //fn plot(width: u32, height: u32) -> impl IntoResponse; -} +//pub trait Plotter { +// //fn plot(width: u32, height: u32, plotting_fun: &dyn Fn(IntoDrawingArea)) -> impl IntoResponse; +// fn plot( +// width: u32, +// height: u32, +// //plotting_fun: &dyn Fn(Box), +// plotting_fun: Box< +// dyn Fn( +// plotters::drawing::DrawingArea< +// plotters::backend::SVGBackend, +// plotters::coord::Shift, +// >, +// ), +// >, +// ) -> impl IntoResponse; +// //fn plot(width: u32, height: u32, plotting_fun: &dyn Fn()) -> impl IntoResponse; +// //fn plot(width: u32, height: u32) -> impl IntoResponse; +//} diff --git a/src/plotting/svg_plotter.rs b/src/plotting/svg_plotter.rs index 5042497..11f91bc 100644 --- a/src/plotting/svg_plotter.rs +++ b/src/plotting/svg_plotter.rs @@ -1,4 +1,4 @@ -use super::plotter::Plotter; +// use super::plotter::Plotter; use axum::http::header; use axum::response::IntoResponse; use plotters::prelude::*; @@ -41,10 +41,8 @@ impl SVGPlotter { root.present().unwrap(); }) } -} -impl Plotter for SVGPlotter { - fn plot(width: u32, height: u32, plotting_fun: SVGPlotFun) -> impl IntoResponse { + pub fn plot(width: u32, height: u32, plotting_fun: SVGPlotFun) -> impl IntoResponse { let mut image_string = String::new(); {