about
Bits: Rust Objects
12/22/2023
0
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
-
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
1.0 Source Code
-
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
1.1.1 Standard String Objects
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
/*-----------------------------------------------------------------------------------------------------------
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
1.2.1 Define Type Point4D
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}}}");
}
}
1.2.2 Demo user-defined type Point4D
/*---------------------------------------------------------------------------------------------------------------------
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
1.3.1 Define Type PointNPrototype
/*
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:
sional space. These points could represent the state of
an electomechanical control system or chemical process.
Unlike
because its
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,
instances, and a method,
access to their internal states.
Special struct methods:
Unlike
cannot be made
member which is a
Because of this, a declaration
would fail to compile. So
Construction and assignment for
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
1.3.2 Demo user-defined type PointNPrototype
/*-----------------------------------------------------------------------------------------------------------
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
/*-------------------------------------------------------------------
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:
long dotted lines, used to make output headings.
short dotted lines, used to show an operation that results
in a specific output.
⇐ Generic function:
So, references are copied; the arguments are not moved.
That means that the instance being analyzed,
still be valid after the call.
Note that using a leading underscore, as in
the compiler not to warn about no use of that variable.
1.5 Demos Execution - main.rs
1.5.1 Structure
#![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 itscode 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
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
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. |
Concept:
Point4D holds data for a point in 3 dimensional spaceand 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.
its format method.
Special struct methods:
Unlike C++, Rust does not provide, nor require special
methods, like overloaded constructors and destructor.
Note that the
above the Point4D declaration requires the Rust
compiler to generate
A trait is very similar to a C# interface, declaring
methods like
Rust types are either
can be
and the struct implements the
The
marker that tells the compiler to copy the memory
footprint of an instance for construction and assign-
ment.
Since it implements
Implementing Copy fails to compile if the struct
contains any non-copy members.
Rust construction and assignment are always either
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
members are relatively small, so this code is
idiomatic.
The
to deplay a simple, debug friendly, representation of
its argument. In this case the argument will be an
instance of Point4D.
Design Note:
methods: the coors methods and getter and setter
methods. At most only one of these is needed.
For simple types like
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
accept arbitrary data.