about
Bits: Rust Objects
12/22/2023
0
Bits Repo Code Bits Repo Docs

Bits: Rust Objects

library and user-defined structs and objects

Synopsis:

This page demonstrates uses of Rust User-Defined types and their objects. The purpose is to quickly acquire some familiarity with user-defined types and their implementations.
  • Syntactically, Rust uses the keyword "struct" instead of "class" to do the same things the other languages do, within the constraints of its language design.
  • Unlike C++, Rust does not define special class methods. Rust types are either copy or move, and most user-defined types are move, so it has no need for them. This eliminates one source of implementation complexity.
  • The compiler will, if asked with an attribute, auto-generate some operations like clone.
  • Also, this is the first set of examples to partition code into several files. That supports readability, may improve translation times, and makes maintenance significantly easier.
Rust Aggregate Type Structure  
Aggregates are types whose instances hold instances of, or handles to, other types, i.e., an array can hold instances of an arbitrary type. Structs and classes can hold named instances or handles to arbitrary other types. There are three ways that happens:
  • Inheritance A Rust struct can inherit only trait declarations. The memory footprint of the struct is not affected by trait inheritances. Unlike C++ and C#, Rust does not support inheritance of implementation.
  • Composition
    When an instance of a Rust type holds one or more instances of other types, the memory footprint of each composed type lies entirely within the memory footprint of its parent instance. We say the parent composes its child elements.
  • Aggregation
    Rust types can hold smart pointer handles to instances of types stored in the native heap, e.g., Box, Rc, and Arc. When an instance of a type holds a handle to instances of other types, the parent's memory footprint holds only the handle, not the entire child instance. We say the parent aggregates the type referenced by the handle. It is possible for a Rust type to hold references to other types, but that is very rare, because Rust's safety invariants make that difficult to accomplish, and that is usually not very useful.
Demo Notes  
All of the languages covered in this demonstration support classes. Each class provides a pattern for laying out a memory footprint and defines how data within are accessed. Unlike all the other languages, Rust uses the struct type to create patterns for object creation. This is simply a syntactical change. Everything that the other languages do with classes Rust does with structs, within the constraints of its language design model. Three of the languages: C++, Rust, and C# provide generics. Each generic function or class is a pattern for defining functions and classes of a specific type. Thus a generic is a pattern for making patterns. The other two, Python and JavaScript, are dynamically typed and already support defining functions and classes for multiple types, e.g., no need for generics. This demonstration illustrates use of classes and objects, which for C++, Rust, and C#, are defined in a stackframe or in the heap or both. All data for Python and JavaScript are stored in managed heaps.
The examples below show how to create user defined types with emphasis on illustrating syntax and basic operations.

1.0 Source Code

Source code is presented in this page using a series of code blocks:
  • Demonstration of standard String creation and uses
  • Demonstration of standard Vec<i32> creation and uses
  • Definition of user-defined Point4D type with 3 floating point coordinates and one time coordinate.
    • Relatively simple type to introduce Rust user-defined type syntax
    • Copy type with copy construction and copy assignment
  • Demonstration of copy type Point4D creation and uses
  • Definition of move type PointN with N floating point coordinates
    • Prototype for N-dimensional point type
    • Move type with move construction and move assignment
  • Demonstration of move type PointN creation and uses
  • Presentation and discussion of analysis and display functions
  • Program structure

1.1 Rust std::library Objects

The next few blocks illustrate creation and use of instances of types defined by the Rust std::lib.

1.1.1 Standard String Objects

This Bit demonstration starts by creating and using operations of instances of String defined as part of the standard library. The code consists of a single function demo_string_objects().
Figure 1. String Layout
Rust strings, unlike those in many other languages, do not necessarily consist of characters all of the same size. Instead, characters are utf-8 which may have sizes from 1 to 4 bytes. That means that Rust strings have built-in support for characters used in other languages like Kanji, Arabic, Hindi, and french that use diacritics like â and other non-ASCII symbols. This power comes at the cost of more complex iteration. Indexing returns bytes, which may not be complete characters. For that reason Rust provides the string iterator chars() which uses a bit encoding scheme to find character boundaries. We won't dig into that iteration scheme in this Bit, but you can find a complete description in the Rust Bite for Strings. You can find all the code for this demo here and code for the entire Bits repository here.
The two panels below are separated by a "splitter bar". You can drag the bar horizontally to view hidden code or click in either panel to increase its width.
fn demo_string_objects() {
/*--------------------------------------------------------                                                
  demo standard String:
  - create and initialize instances
  - move construction
  - move assignment
  - selected operations
*/
  show_note("demo std String", 40);
  let mut s:String = "a string".to_string();
  println!("  s: {:?}\n", s);

  show_op("let s1 = s : move construction");
  let mut s1 = s;  // move construction
  println!("s1: {:?}\n", s1);
  // s now invalid, been moved : uncomment to see error msg
  // println!("{:?}", s);

  show_op("s1 += \" and more\" : one of many operations");
  s1 += " and more";
  println!("s1: {:?}\n", s1);

  show_op("s = s1.clone : move assignment of clone");
  s = s1.clone();
  // clone copies s1's resources so s1 is still valid
  println!("s: {:?}", s);
  println!("s1: {:?}\n", s1);

  show_op("s1 = s1 + \" words\"");
  s1 += " words";
  show_op("s = s1 : move assignment");
  s = s1;
  println!("s: {:?}\n", s);
  // s1 now invalid, been moved

  // String stored in heap
  show_op("let mut h_str = Box::new(\"...\")");
  let mut h_str = Box::new("heap string".to_string());
  println!("h_str: {:?}", h_str);

  *h_str += " and some more";
  println!("h_str: {:?}", h_str);

}
              

  
  
  
  
  
----------------------------------------                                                  
  demo std String
----------------------------------------
  s: "a string"

--- "let s1 = s : move construction" ---
s1: "a string"

--- "s1 += \" and more\" : one of many operations" ---
s1: "a string and more"

--- "s = s1.clone : move assignment of clone" ---
s: "a string and more"
s1: "a string and more"

--- "s1 = s1 + \" words\"" ---
--- "s = s1 : move assignment" ---
s: "a string and more words"

--- "let mut h_str = Box::new(\"...\")" ---
h_str: "heap string"
h_str: "heap string and some more"

















1.1.2 Standard Vec Objects

This Bit demonstration starts by creating and using operations of instances of String and Vector<T>, both defined as part of the standard library. The code consists of a single function demo_standard_objects(). You can find all the code for this demo here and code for the entire Bits repository here.
/*-----------------------------------------------------------------------------------------------------------
  demo standard Vector
  - create and initialize instances
  - move construction
  - move assignment
  - selected operations
*/
fn demo_vector_objects() {
  show_note("demo std vector", 40);
  let mut v: Vec<i32> = vec![1, 2, 3, 2, 1];
  println!("  v: {:?}\n", v);

  show_op("let v1 = v : move construction");
  let mut v1 = v;  // move construction
  println!("  v1: {:?}\n", v1);
  // v now invalid, been moved : uncomment to see error msg
  // println!("{:?}", v);

  show_op("v1[1] = -2 : one of many operations");
  v1[1] = -2;
  println!("  v1: {:?}\n", v1);

  show_op("v = v1.clone : move assignment of clone");
  v = v1.clone();
  // clone copies v1's resources so v1 is still valid
  println!("  v: {:?}", v);
  println!("  v1: {:?}\n", v1);

  show_op("a = &v[1..3]");
  // create array from vector slice
  let a: &[i32] = &v[1..3];
  println!("  a: {:?}\n", a);

  show_op("v2 is collect from slice &v[1..3]");
  let v2 = (1..3).map(|i| v1[i]).collect::<Vec<i32>>();
  println!("  v2: {:?}\n", v2);

  show_op("v = v2 : move assignment");
  v = v2;
  println!("  v: {:?}\n", v);
  // v2 now invalid, been moved

  // Vec stored in heap
  show_op("let mut h_vec = Box::new(\"...\")");
  let mut h_vec = Box::new(vec![1, 2, 3, 4, 5]);
  println!("  h_vec: {:?}\n", h_vec);

  show_op("(*h_vec)[2] += 2");
  (*h_vec)[2] += 2;
  println!("  h_vec: {:?}", h_vec);

  show_op("h_vec[2] += 2, uses auto deref");
  h_vec[2] += 2;
  println!("  h_vec: {:?}\n", h_vec);

}
              






----------------------------------------                                                  
  demo std vector
----------------------------------------
  v: [1, 2, 3, 2, 1]

--- "let v1 = v : move construction" ---
  v1: [1, 2, 3, 2, 1]

--- "v1[1] = -2 : one of many operations" ---
  v1: [1, -2, 3, 2, 1]

--- "v = v1.clone : move assignment of clone" ---
  v: [1, -2, 3, 2, 1]
  v1: [1, -2, 3, 2, 1]

--- "a = &v[1..3]" ---
  a: [-2, 3]

--- "v2 is collect from slice &v[1..3]" ---
  v2: [-2, 3]

--- "v = v2 : move assignment" ---
  v: [-2, 3]

--- "let mut h_vec = Box::new(\"...\")" ---
  h_vec: [1, 2, 3, 4, 5]

--- "(*h_vec)[2] += 2" ---
  h_vec: [1, 2, 5, 4, 5]
--- "h_vec[2] += 2, uses auto deref" ---
  h_vec: [1, 2, 7, 4, 5]




















1.2 Source Code - Points.rs

A Rust struct is a pattern for laying out a type's data in memory with member functions, called methods, for all of its operations. Making data members private ensures that struct methods have valid data for their operations. A type designer may choose to selectively allow user data modification.

1.2.1 Define Type Point4D

Point4D is a user-defined type representing a point in 4-dimensional space-time. It declares the data held by instances of the type: three floating point coordinates and a DateTime<Local> variable, and defines methods to create and modify instances. You can find all the code for this demo here and code for the entire Bits repository here.
use std::fmt::*;
use chrono::prelude::*;

/*---------------------------------------------------------
  Declare Point4D struct
  - similar in function to a C++ class
  - Request compiler to implement traits: Debug, Copy & Clone
  - Can be Copy since all members are copy
  - So, construction and assignment are copy operations
  - Demos two types of member access, only one needed
*/
#[derive(Debug, Copy, Clone)]
pub struct Point4D {
  x: f64,
  y: f64,
  z: f64,
  t: DateTime<Local>,
}
/*-- implement constructor function new --*/
impl Point4D {
  pub fn new() -> Point4D {
    Point4D { x: 0.0, y: 0.0, z: 0.0, t: Local::now() }
  }
  /*------------------------------------------------
    These methods can be used to set or retrieve
    values of Point4D instance coordinates, as they
    return a mutable reference. Note similarity with
    C++ code.
  */
  pub fn coor_x(&mut self) -> &mut f64 {
    &mut self.x
  }
  pub fn coor_y(&mut self) -> &mut f64 {
    &mut self.y
  }
  pub fn coor_z(&mut self) -> &mut f64 {
    &mut self.z
  }
  pub fn time(&self) -> String {
    self.t.format("%a %b %e %Y, %T").to_string()
  }
  pub fn update_time(&mut self) {
    self.t = Utc::now().with_timezone(&Local);
  }
  /*------------------------------------------------
    An alternate design for mutating coordinates.
    This set has twice as many methods, but may
    make access and mutation more obvious when
    reading source code.
  */
    pub fn get_x(&self) -> &f64 {
      &self.x
    }
    pub fn get_y(&self) -> &f64 {
      &self.y
    }
    pub fn get_z(&self) -> &f64 {
      &self.z
    }
    pub fn set_x(&mut self, x: f64) {
      self.x = x
    }
    pub fn set_y(&mut self, y: f64) {
      self.y = y
    }
    pub fn set_z(&mut self, z: f64) {
      self.z = z
    }
  /*
    For simple types like this it would be reasonable to
    make x, y, z public and remove the getter and setter
    functions.
  */
  /*-----------------------------------------------------
    show method displays instance values
    - uses helper function indent
  */
  pub fn indent(ch:char, n:usize) -> String {
    (0..n).map(|_i| ch).collect::<String>()
    // equivalent to:
    //   let ind = (0..n).map(|_i| ch).collect::<String>();
    //   ind
    // - (0..n) is an iterator yielding values 0 through n-1
    // - map sets each iterator item to same char ch
    // - collect appends each char to temporary String
    // ind defines return value, the collected String
  }
  pub fn show(&self, nm:&str, ind:usize) {
    let indstr = Point4D::indent(' ', ind);
    print!("{indstr}{nm} : Point4D {{\n  ");
    print!("{indstr}{0}, ", self.x);
    print!("{indstr}{0}, ", self.y);
    println!("{indstr}{0},", self.z);
    println!("{indstr}  {}", Local::now().format("%a %b %e %Y, %T"));
    println!("{indstr}}}");
  }
}

Concept:

Point4D holds data for a point in 3 dimensional space
and a time when something was at that point.

These points could be collected into a vector to
represent a trajectory of an aircraft or a machine
tool cutting edge.




Rust struct syntax:
Rust uses structs as patterns for laying out objects
in memory, much like C++ uses classes.

Methods are not declared within the struct body.
Instead, they are defined in a seperate "impl" scope.

Unlike C++, the input reference to self must be supp-
lied explicitly, where C++ passes that implicitly, i.e.,
for C++ it doesn't appear in user code.

Mutability must be explicit. If not declared, any
attempt to change value will fail to compile.

Return types are declared explicitly, as shown here
for return of a string representation of Point4D's
value of local time.

t:DateTime<Local> is converted to a string using
its format method.

Special struct methods:

Unlike C++, Rust does not provide, nor require special
methods, like overloaded constructors and destructor.

Note that the #[derive(Debug, Copy, Clone)] just
above the Point4D declaration requires the Rust
compiler to generate Debug, Copy and Clone traits.

A trait is very similar to a C# interface, declaring
methods like clone() for the Clone trait.

Rust types are either Copy or Move (!Copy). A type
can be Copy if, and only if, all its members are Copy
and the struct implements the Copy trait.

The Copy trait declares no methods. It is simply a
marker that tells the compiler to copy the memory
footprint of an instance for construction and assign-
ment.

Since it implements Copy, Point4D is a copy type.
Implementing Copy fails to compile if the struct
contains any non-copy members.

Rust construction and assignment are always either
Copy or Move operations, so there are either copy
construction and assignment or move construction and
assignment, but not both. Moves are transfers of
ownership of the source's resources.

A clone operation copies resources from the cloned
instance to the new clone. It is not an ownership
transfer of those resources.

For purposes of performance, idiomatic Rust code
avoids expensive copy operations wherever possible,
and when used, makes that use explicit with a call
to clone().

Point4D is relatively cheap to copy because its
members are relatively small, so this code is
idiomatic.

The Debug trait declares the fmt method and is used
to deplay a simple, debug friendly, representation of
its argument. In this case the argument will be an
instance of Point4D.

Design Note:

Point4D implements two distinct sets of accessor
methods: the coors methods and getter and setter
methods. At most only one of these is needed.

For simple types like Point4D it would be very reason-
able to simply make the data x, y, and z public. That
would not be appropriate for t because the t data
member must be of type DateTime<Local> and can't
accept arbitrary data.

1.2.2 Demo user-defined type Point4D

Code in this block demonstrates creation and use of instances of the user-defined type Point4D. All the code is presented as part of a single function: demo_user_defined_point4d(). You can find all the code for this demo here and code for the entire Bits repository here.
/*---------------------------------------------------------------------------------------------------------------------
  demo user-defined type Point4D
  - create and initialize instances
  - copy construction
  - copy assignment
  - selected operations
*/
fn demo_user_defined_point4d() {

  show_note("instances of user-defined type Point4D", 35);
  println!();

  /*-- create instance of Point4D ---*/
  show_op("let mut p1 = Point4D::new()");
  let mut p1 = Point4D::new();
  p1.show("p1", 2);
  p1.set_x(42.0);
  p1.set_y(-3.0);
  p1.set_z(2.0);
  p1.show("p1", 2);
  /*-- show p1's type ---------------*/
  p1.show("p1", 2);
  println!();

  show_op("using Debug trait with println!");
  println!("p1: {:?}\n", p1);

  /*-- show one of its operations ---*/
  show_op("let p1a = p1.clone()");
  let p1a = p1.clone();
  p1a.show("p1.clone()", 2);

  println!("\n  using PointN<f64>::coor_x function:");
  *(p1.coor_x()) = 84.0;
  p1.show("p1", 2);

  println!("\n  using PointN<f64>::coor_y function:");
  *p1.coor_y() = 84.0;
  p1.show("p1", 2);

  println!("\n  using PointN<f64>::coor_z function:");
  let rz = p1.coor_z();
  *rz = 84.0;
  p1.show("p1", 2);

  println!("\n  updating time value:");
  /*-- delay 5 secs for update_time demo --*/
  println!("  delaying 5 seconds before time update");
  use std::time::Duration;
  let dur:Duration = Duration::from_secs(5);
  std::thread::sleep(dur);

  p1.update_time();
  p1.show("p1", 2);
  println!();

  show_op("let p2 = p1 : copy construction");
  let mut p2 = p1;
  p2.show("p2", 2);
  p1.show("p1", 2);

  show_op("*p2.coor_x() = 42.0");
  *p2.coor_x() = 42.0;
  p2.show("p2", 2);

  show_op("p1 = p2 : copy assignment");
  p1 = p2;
  p1.show("p1", 2);
  p2.show("p2", 2);
  println!();

  show_note("Point4D objects in heap", 40);
  let mut h_point = Box::new(Point4D::new());
  // show_type(&h_point, "h_point");
  /*
    Point4D does not implement trait DerefMut, so code has to
    explicitly deref, as shown here.
  */
  *h_point.coor_x() = 2.0;
  *h_point.coor_y() = 1.0;
  *h_point.coor_z() = 0.0;
  (*h_point).show("h_point", 2);
  println!();

  show_op("let h_point1 = h_point.clone()");
  let h_point1 = h_point.clone();
  (*h_point1).show("h_point1", 2);
  println!();

}


              
                

---------------------------------------------
  instances of user-defined type Point4D
---------------------------------------------

--- "let mut p1 = Point4D::new()" ---
  p1 : Point4D {
    0,   0,   0,
    Wed Dec 20 2023, 12:02:27
  }
  p1 : Point4D {
    42,   -3,   2,
    Wed Dec 20 2023, 12:02:27
  }
  p1 : Point4D {
    42,   -3,   2,
    Wed Dec 20 2023, 12:02:27
  }

--- "using Debug trait with println!" ---
p1: Point4D { x: 42.0, y: -3.0, z: 2.0, t: 2023-12-20T12:02:27.848829800-06:00 }          

--- "let p1a = p1.clone()" ---
  p1.clone() : Point4D {
    42,   -3,   2,
    Wed Dec 20 2023, 12:02:27
  }

  using PointN<f64>::coor_x function:
  p1 : Point4D {
    84,   -3,   2,
    Wed Dec 20 2023, 12:02:27
  }

  using PointN<f64>::coor_y function:
  p1 : Point4D {
    84,   84,   2,
    Wed Dec 20 2023, 12:02:27
  }

  using PointN<f64>::coor_z function:
  p1 : Point4D {
    84,   84,   84,
    Wed Dec 20 2023, 12:02:27
  }

  updating time value:
  delaying 5 seconds before time update
  p1 : Point4D {
    84,   84,   84,
    Wed Dec 20 2023, 12:02:32
  }

--- "let p2 = p1 : copy construction" ---
  p2 : Point4D {
    84,   84,   84,
    Wed Dec 20 2023, 12:02:32
  }
  p1 : Point4D {
    84,   84,   84,
    Wed Dec 20 2023, 12:02:32
  }
--- "*p2.coor_x() = 42.0" ---
  p2 : Point4D {
    42,   84,   84,
    Wed Dec 20 2023, 12:02:32
  }
--- "p1 = p2 : copy assignment" ---
  p1 : Point4D {
    42,   84,   84,
    Wed Dec 20 2023, 12:02:32
  }
  p2 : Point4D {
    42,   84,   84,
    Wed Dec 20 2023, 12:02:32
  }

----------------------------------------
  Point4D objects in heap
----------------------------------------
  h_point : Point4D {
    2,   1,   0,
    Wed Dec 20 2023, 12:02:32
  }

--- "let h_point1 = h_point.clone()" ---
  h_point1 : Point4D {
    2,   1,   0,
    Wed Dec 20 2023, 12:02:32
  }

              

1.3 Point as Move Type

This section extends Point4D to an N-dimensional point storing its coordinate data in a std::Vec<T>.

1.3.1 Define Type PointNPrototype

PointNPrototype is a prototype for an N-dimensional point. It declares data held by instances of the type as a vector of floats, and defines methods to create and modify instances. This type will be completed in the next, Generic, Bit. PointNPrototype is a Move type. Construction and assignment are move operations that are created by the compiler, as is the drop operation, similar to a C++ destructor. That means that Rust developers do not create special class methods for them. PointNPrototype requests the compiler to implement the Debug and Clone traits using #[derive(Debug, Clone)]. Attempting to make it a copy type by including the Copy trait would result in compile failure because its struct contains a non-copy member. So, both construction and assignment are move operations, which transfers ownership of the source's resources to the destination. That usually entails copying a pointer and two counters. That is fast, but leaves the source in an invalid state. Attempting to use the source again results in compile failure. You can find all the code for this demo here and code for the entire Bits repository here.
/*
PointNPrototype
  - Request compiler implement traits Debug & Clone
  - Can't be Copy since members are not copy
  - So, construction and assignment are move operations
  - This demo will be expanded into useful type in
    next Bit: Rust_GenericBit
*/
#[derive(Debug, Clone)]   // can't make Copy type
pub struct PointNPrototype {
  coords: Vec<f64>        // not copy type
}
impl PointNPrototype {
  pub fn new() -> PointNPrototype {
    PointNPrototype {
      coords: vec![]
    }
  }
  /*-- initialize from elements of array slice --*/
  pub fn init(&mut self, arr: &[f64]) -> &mut PointNPrototype {
    self.coords = (0..arr.len()).map(|i| arr[i]).collect::<Vec<f64>>();
    // arr is an array slice, e.g., a view into an array
    // (0..arr.len()) is iterator returning integers from 0 to arr.len() - 1
    // map(|i| arr[i]) returns arr[i] for each iterator item i
    // collect::<Vec<f64>>() collects Vec of those arr values.
    self
  }
  /*-- make coordinates accessible for reading and writing --*/
  pub fn coors(&mut self) -> &mut Vec<f64> {
    &mut self.coords
  }
}

Concept:

PointNPrototype holds data for a point in N dimen-
sional space. These points could represent the state of
an electomechanical control system or chemical process.

Unlike Point4d this class cannot support copy operations
because its Vec<f64> data member is not a copy
type.

Prototype syntax:
PointNPrototype is a prototype for a type we will
implement completely in the next Bit: Bits_GenericRust.
It is included here to illustrate the distinction between
Copy and Move types.

It includes two methods, new() and
init(&mut self, arr: &[f64]) to create and initialize
instances, and a method, coors(&mut self) to provide
access to their internal states.

Special struct methods:

Unlike Point4D, PointNPrototype is a Move type. It
cannot be made Copy because it has a Vec<f64>
member which is a Move type.

Because of this, a declaration #[derive(Copy, ...)]
would fail to compile. So PointNPrototype leaves the
Copy trait out of that declaration.

Construction and assignment for PointNPrototype
are move operations. There are no corresponding copy
operations.

Note that it does support making clones so code can
move clones into the destination instances, ensuring
that the original cloned instance remains valid.

A clone operation copies resources from the cloned
instance to the new clone. It is not an ownership
transfer of those resources.

For purposes of performance, idomatic Rust code avoids
copy operations wherever possible, and when used,
makes that use explicit with a call to clone().

1.3.2 Demo user-defined type PointNPrototype

Code in this block demonstrates creation and use of instances of the user-defined type PointNPrototype. All the code is created as part of a single function: demo_user_defined_pointnprototype(). You can find all the code for this demo here and code for the entire Bits repository here.
/*-----------------------------------------------------------------------------------------------------------
  demo user-defined type PointNPrototype
  - create and initialize instances
  - move construction
  - move assignment
  - selected operations
*/
fn demo_user_defined_pointnprototype() {

  show_note(
    "instances of user-defined type PointNPrototype",
    35
  );
  println!();

  /*-- create instance of PointNPrototype ---*/
  show_op("create PointNPrototype, print using Debug trait");
  let mut q = PointNPrototype::new();
  q.init(&[1.0, 2.0, 3.0]);
  println!("  q: {:?}", q);

  show_op("let q1 = q: move construction");
  let mut q1 = q;
  println!("  q1: {:?}", q1);
  // code below will fail to compile, q was moved
  // println!("  q: {:?}", q);

  q1.coors()[1] = -2.0;
  show_op("q = q1: move assignment");
  q = q1;  // q owns resources given by q1 so valid
  println!("  q: {:?}", q);
  // q1 has been move so no longer valid

  show_op("let q2 = q.clone()");
  let q2 = q.clone();
  println!("  q2: {:?}\n", q2);

  show_note("PointNPrototype objects in heap", 40);
  let mut h_point_prototype =
    Box::new(PointNPrototype::new());
  h_point_prototype.init(&[3.0, 2.5, 2.0]);
  println!("  h_point_prototype: {:?}", h_point_prototype);

  show_op("  h_point_prototype.coors()[0] = -3.0;");
  h_point_prototype.coors()[0] = -3.0;
  println!("  h_point_prototype: {:?}", h_point_prototype);

  show_op("let h_point_prototype1 = h_point_prototype.clone()");
  let h_point_prototype1 =
    h_point_prototype.clone();
  println!("  h_point_prototype1: {:?}", h_point_prototype1);

}
              
                








-------------------------------------------------------
  instances of user-defined type PointNPrototype
-------------------------------------------------------

--- "create PointNPrototype, print using Debug trait" ---
  q: PointNPrototype { coords: [1.0, 2.0, 3.0] }
--- "let q1 = q: move construction" ---
  q1: PointNPrototype { coords: [1.0, 2.0, 3.0] }
--- "q = q1: move assignment" ---
  q: PointNPrototype { coords: [1.0, -2.0, 3.0] }
--- "let q2 = q.clone()" ---
  q2: PointNPrototype { coords: [1.0, -2.0, 3.0] }

----------------------------------------
  PointNPrototype objects in heap
----------------------------------------
  h_point_prototype: PointNPrototype { coords: [3.0, 2.5, 2.0] }
--- "  h_point_prototype.coors()[0] = -3.0;" ---
  h_point_prototype: PointNPrototype { coords: [-3.0, 2.5, 2.0] }
--- "let h_point_prototype1 = h_point_prototype.clone()" ---
  h_point_prototype1: PointNPrototype { coords: [-3.0, 2.5, 2.0] }                                        























              

1.4 Source Code - Analysis.rs

Code in this block provides analysis and display functions used in the other code blocks. You can find all the code for this demo here and code for the entire Bits repository here.
/*-------------------------------------------------------------------
  analysis.rs
  - provides analysis and display functions for Objects demo.
-------------------------------------------------------------------*/

use std::fmt::*;

pub fn show_note(s:&str, n:usize) {
  let border = (0..n).map(|_| "-").collect::<String>();
  println!("{}", &border);
  println!("  {}", s);
  println!("{}", &border);
}

pub fn show_op(s:&str) {
  println!("--- {:?} ---", s);
}

/*---------------------------------------------------------
  Show input's call name and type
  - doesn't consume input
  - show_type is generic function with Debug bound.
    Using format "{:?}" requires Debug.
  - used in show_type_scalar
*/
pub fn show_type<T:Debug>(_t: &T, nm: &str) {
    let typename = std::any::type_name::<T>();
    println!("{nm:?}, type: {typename:?}");
}

Analysis and Display Functions:

This code block contains definitions of three functions:
   - two non-generic display functions
   - one generic analysis function

Non-generic functions:

show_note(s:&str, n:usize) displays text between
long dotted lines, used to make output headings.

show_op(s:&str) displays text inserted between
short dotted lines, used to show an operation that results
in a specific output.






Generic function:

show_type<T>'s arguments are passed by reference.
So, references are copied; the arguments are not moved.

That means that the instance being analyzed, _t, will
still be valid after the call.

Note that using a leading underscore, as in _t, tells
the compiler not to warn about no use of that variable.

1.5 Demos Execution - main.rs

All code in this section is used to conduct demonstrations, e.g., it is the contents of main() and the functions it invokes. That is broken up into sections for library types String and Vec<T>, and user-defined types Point4D and PointNPrototype.

1.5.1 Structure

The code block, below, demonstrates program structure for main.rs. It uses four functions to demonstrate Strings, Vec<T>s, and two user-defined types Point4D and PointNPrototype. The program imports two modules, one that defines the point types, and one that supplies a set of functions used for analysis and display of these types. This partitioning makes the program code easier to understand and to maintain. You can find all the code for this demo here and code for the entire Bits repository here.
#![allow(dead_code)]
#![allow(clippy::approx_constant)]

/*---------------------------------------------------------
  rust_objects::main.rs
  - Demonstrates creation and use of Rust objects
  - Rust uses struct instead of class to create objects
*/
/*-----------------------------------------------
Note:
Find all Bits code, including this in
https://github.com/JimFawcett/Bits
You can clone the repo from this link.
-----------------------------------------------*/

mod analysis_objects;     // identify module source file
use analysis_objects::*;  // import public functs and types
mod points_objects;       // identify module source file
use points_objects::*;    // import public functs and types

fn demo_string_objects() {
  /*-- code elided --*/
}
fn demo_vector_objects() {
  /*-- code elided --*/
}
fn demo_user_defined_point4d() {
  /*-- code elided --*/
}
fn demo_user_defined_pointnprototype() {
  /*-- code elided --*/
}

/*---------------------------------------------------------
  Demo object instances in action
*/
fn main() {
    show_note("demonstrate object creation and use", 50);

    demo_string_objects();
    demo_vector_objects();
    demo_user_defined_point4d();
    demo_user_defined_pointnprototype();

    print!("\nThat's all Folks!\n\n");
}

Structure:

This is the first Rust demonstration that partitions its
code into more than one file.

Modules:

Rust defines modules to support partitioning. Here we
have two of them, "analysis_objects" and "points_objects".

A Rust module is simply a file that implements program
functionality focused in a single area, but has no main
function, as only one main is allowed per program.

Both module files, analysis_objects.rs and
points_objects.rs are placed in the src directory along
with main.rs. That makes it simple to import them, e.g.,
no path required.

That also means that a module can depend on code in
another module.

Functions:

All of the demonstration code is divided into functions
for library types and user-defined types. Each function
creates an instance of a type, illustrates access to
values of the type and shows how to modify them.

These demos do not cover type behaviors exhaustively.
They are intended to be relatively simple discussions
that are easy to digest and remember.







Execution:

Execution starts with an entry to the function main().
Each demonstraton is implemented with a type specific
demo function.

2.0 Build

The left panel shows a build using cargo in a terminal. Using VS Code, you can also initiate a build from the Run menu, selecting either debug or run without debugging. What happens in that run process is governed by the launch.json file in the .vscode folder. A view of that is shown in Fig 2., below. The right panel shows the Cargo.toml file contents that defines dependencies. Note that specified dependencies are likely to have their own dependencies and building on Windows induces a dependency.
Terminal
cargo.toml
C:\github\JimFawcett\Bits\Rust\rust_objects
> cargo build
   Compiling autocfg v1.1.0
   Compiling winapi v0.3.9
   Compiling libc v0.2.144
   Compiling num-traits v0.2.15
   Compiling num-integer v0.1.45
   Compiling time v0.1.45
   Compiling chrono v0.4.24
   Compiling rust_objects v0.1.0 (C:\github\JimFawcett\Bits\Rust\rust_objects)
    Finished dev [unoptimized + debuginfo] target(s) in 3.36s
C:\github\JimFawcett\Bits\Rust\rust_objects
[package]
name = "rust_objects"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
chrono = "0.4.24"
num-traits = "0.2.15"

3.0 VS Code View

The code for this demo is available in github.com/JimFawcett/Bits. If you click on the Code dropdown you can clone the repository of all code for these demos to your local drive. Then, it is easy to bring up any example, in any of the languages, in VS Code. Here, we do that for Rust\rust_hello_objects. Figure 1. VS Code IDE - Rust Objects Figure 2. Rust Launch.JSON Figure 3. Debugging Rust Objects

3.0 References

Reference Description
Rust Story E-book with seven chapters covering most of intermediate Rust
Rust Bites Relatively short feature discussions
Rust Collections Table of Rust collections with each row containing a type declaration, description, and a diagram. Contains rows for array, tuple, Struct, Vec, VecDeque, HashMap, String, and str.