about
Rust Bits
9/06/2022
CodeBits Repo Docs

Rust Bits

organized bits of syntax, idoms, and patterns

Quick Status Recent page additions getting started adding content from IaP will replace Idioms and Patterns UI layout now stable, needs more planned content
Bits are small compilable pieces of code that illustrate syntax, idioms, and small patterns. They support learning a new language by comparing bits with those of a language you already know. For now, that is limited to the programming languages C++, Rust, and C#. You can compare These Rust bits with C++ or C# bits using the navigation buttons below for each bit. If you have a wide screen or dual monitors you can right click on either of the show buttons and select "Open Link in New Window" to compare the Bits for two languages side-by-side.
Rust code bits
What is this?Code demoCommentary
Hello World
The traditional first look at a new language
References:
// HelloWorld::main.rs fn main() { println!("Hello, world!"); } In Rust, functions begin with the fn declarator, accept arguments in a parentisized list, encapsulate an executable code in a set of statements enclosed in braces, and may declare a return type. fn g(ArgType a) -> ReturnType { ... } If functions end with an expression, the value is returned; no return statement necessary. Those that end with statements, e.g., end with a semicolon, return the unit (), signifying nothing.
Each executable Rust program starts in a "main" function, which often calls other functions supplying operational details. There can be only one main in an executable.
Hello Objects
Demonstrates how Types are defined and instances created. The type DemoObj is defined above its main function and instances dob and cdob are created in main.
References:
// CreateObject::main.rs /*------------------------------------- - Declare DemoObj struct, like C++ class - Rqst compiler impl traits Debug & Clone */ #[derive(Debug, Clone)] pub struct DemoObj { name : String } /*-- implement functions new and name --*/ impl DemoObj { pub fn new(obj_name: &str) -> DemoObj { DemoObj { name: obj_name.to_string() } } pub fn name(&self) -> &str { &self.name } pub fn about() { print!("Demo creation and use of objects"); } } /*-- Demo DemoObj instance in action --*/ fn main() { print!( "\n -- demonstrate object creation --\n" ); let dob = DemoObj::new("Frank"); print!( "\n DemoObj instance name is {:?}", dob.name() ); let cdob = dob.clone(); print!( "\n name of clone is {:?}", cdob.name() ); print!("\n "); DemoObj::about(); print!("\n\n That's all Folks!\n\n"); } Types are defined with structs, like DemoObj on the left. Functionality is provided through methods, e.g., functions associated with the struct. All methods are declared and defined in impl { ... } blocks.
The function DemoObj::new(&str) is a static method that creates an instance of DemoObj and endows it with a name. Function name(&self) is a none-static method that accepts a reference to its instance and returns its name.
Static functions do not take a reference to self and so cannot access member data. They act like ordinary functions but are accessed with their type followed by colons followed by the function. Non-static functions take a reference to self, can access member data, and are accessed from an object name followed by a period.
Types and functions declare public accessibility with the pub keyword. Functions declare return types with the -> SomeType syntax following the parameter list.
Macro names end with ! like the print!("..."); statement.
The code at the left declares the type and operations for DemoObj. C:\...\CreateObject\RustObject> cargo run -q -- demonstrate object creation -- DemoObj instance name is "Frank" name of clone is "Frank" Demo creation and use of objects That's all Folks!
Hello Data
This section demonstrates data binding, copy, and move operations for primitive and one typical composite type, the standard vector.
References:
/* DataOps::main.rs - demo Rust Data Ops */ fn main() { print!( "\n -- demo data operations --\n" ); /* Primitive data types: i32, f64, ... occupy contiguous regions of memory, so they satisfy the copy trait */ print!("\n -- integer ops --"); let mut x :i32 = 42; let y = x - 2; // copy construction print!("\n x = {}, y = {}", x, y); x = y; // copy assign print!("\n after copy assign: x = y"); print!("\n x = {}, y = {}\n", x, y); /* Most non-primitive types: Vec<T>, String, ... occupy memory on stack and heap, so they do not satisfy the copy trait */ print!("\n -- Vec ops --"); let v:Vec<i32> = vec![1,2,4]; print!("\n v = {:?}", v); let w = v; // move construction print!( "\n after move construction: let w = v:" ); print!("\n w = {:?}", w); print!( "\n now v is invalid (been moved)\n" ); let x = w.clone(); // clone construction print!( "\n after clone oper: let x = w.clone():" ); print!("\n w = {:?}", w); print!("\n x = {:?}", x); println!("\n\n That's all Folks!!\n\n"); } Binding means to associate some name with a region of memory that holds a value of some type. let mut x:i32 = 42; Here, a mutable variable x is bound to the value "42" stored in a location defined by the compiler. let y = x - 2; binds the variable name y to an i32 sized location filled with the value of x - 2. Primitive types like i32 and f64 occupy a single contiguous memory block and are copied with a mem-copy operation. We say these are "copy" types. Compound types like arrays, tuples, and structs with all copy fields are also copy types. Library types like String, Vector, Dequeue, ... are not contigous. They consist of a control block on the stack and a body of data stored in the heap. They cannot be mem-copied in one pass and so are moved. The source's data pointer is copied into the destination control block. Since Rust allows only one owner of data, the source becomes invalid. We say these are "move" types.
Rust build and execute C:\...\DataOperations\RustData> cargo run -q -- demonstrating data operations -- -- integer ops -- x = 42, y = 40 after copy assign: x = y x = 40, y = 40 -- Vec ops -- v = [1, 2, 4] after move construction: let w = v: w = [1, 2, 4] now v is invalid (been moved) after clone operation: let x = w.clone(): w = [1, 2, 4] x = [1, 2, 4] That's all Folks!!
Iteration
Iteration is the process of stepping through a collection and, perhaps, doing something with each item encountered. All iterators have the same simple interface and supply the same set of adapters. That enables declarative programming for examining and modifying the collection's members. They have internal implementations that use knowledge of how their collection is structured.
References:
/*-- Iteration --------------------------------*/ /* Rust std::String ---------------- Rust std::String is a container of utf8 chars with no null terminator. - utf8 characters may consist of 1 up to 4 bytes, so String instances can not be indexed. Character boundaries are defined by specific byte sequences, used by String's chars() iterator to return characters. */ fn string_iteration() { let test_string = String::from("a test string"); /* chars() returns iter over utf8 chars */ let mut iter = test_string.chars(); print!( "\n utf8 characters from {:?}:\n ", &test_string ); loop { /*-- iter.next returns std::Option --*/ let opt = iter.next(); if opt == None { break; } /*-- unwrap char from Some(char) --*/ print!("{} ",opt.unwrap()); } let ls = test_string.as_str(); print!("\n test_string: {:?}", ls); println!(); } fn idomatic_string_iteration() { let test_string = String::from("another test string"); print!( "\n idiomatic utf8 chars from {:?}:\n ", &test_string ); for ch in test_string.chars() { /*-- option handling implicit here --*/ print!("{} ",ch); } /*-- nth(i) returns Option --*/ let i = 1; let rslt = &test_string.chars().nth(i); if let Some(ch) = rslt { print!( "\n at index {} char of {:?} is {}", i, test_string, ch ); } else { print!("\n index {} out of range", i); } let ls = test_string.as_str(); print!("\n test_string: {:?}", ls); println!(); } /*----------------------------------------------- demonstrate chars(), is_alphabetic, is_..., for_each, filter, and collect There are many iterator adapters. These are some of the most often used. */ fn string_adapters() { let ls = "abc123"; /*-- are all chars alphabetic --*/ print!( "\n {:?} is alphabetic {}", ls, ls.chars().all(|c| {c.is_alphabetic()}) ); /*-- are all chars alphanumeric? --*/ print!( "\n {:?} is alphanumeric {}", ls, ls.chars().all(|c| {c.is_alphanumeric()}) ); /*-- are all chars numeric? --*/ print!( "\n {:?} is numeric {}", ls, ls.chars().all(|c| {c.is_numeric()}) ); /*-- are all chars ascii? --*/ print!( "\n {:?} is ascii {}", ls, ls.chars().all(|c| {c.is_ascii()}) ); /*-- display chars from str slice --*/ let (min, max) = (2usize, 4usize); if min <= ls.len() && max <= ls.len() && min <= max { let slice = &ls[min..max]; print!( "\n 3rd and 4th chars of {:?} are: ", ls ); slice.chars() .for_each(|c| print!("{}", c)); } else { print!( "\n invalid {} and {} for {}", min, max, ls ) } /*-- from numeric chars in source, ls --*/ print!( "\n numeric chars of {:?} are: {:?}", ls, ls.chars() .filter(|c| c.is_numeric()) .collect::<String>() ); println!(); } /* Rust byte arrays ---------------- Rust arrays have sizes that must be determined at compile-time, even those created on the heap. Rust Vectors have sizes that can be determined at run-time, and they will readily give access to their internal heap-based arrays by taking slices. This is perfectly data-safe, because: - slices have a len() function - even if you index past the end of the array, you can't read or write that memory, because a panic occurs immediately. */ fn define_and_iterate_byte_array() { let ba: [u8;5] = [1,2,3,4,5]; // size is determined at compile-time, even for // arrays created on the heap (unlike C++) let max = ba.len(); print!("\n bytes from byte array:\n ["); /*-- display all but last --*/ for i in 0..max-1 { print!("{}, ", ba[i]); } /*-- display last char --*/ print!("{}]", ba[max-1]); } fn idiomatic_define_and_iterate_byte_array() { let v: Vec<u8> = vec![5,4,3,2,1]; let ba: &[u8] = &v[..]; /*------------------------------------------- type of slice of Vector is byte slice: &[u8] - slices implement len() function - &v[..] slice of all elements of v - &v[m..n] slice of elements m up to, but not including n - Length of slice &v[..] determined by length of v, which can be determined at run-time. */ print!("\n idiomatic bytes from byte array:"); print!( "\n length of byte slice: {}", ba.len() ); let max = ba.len(); /*-- print all but the last --*/ print!("\n ["); for item in ba.iter().take(max-1) { print!("{}, ", item); } /*-- print last one without trailing comma --*/ print!("{}]", ba[max - 1]); print!( "\n printing array with implicit iteration:" ); print!("\n {:?}", ba); } fn main() { print!("\n -- demonstrate iteration --\n"); print!("\n -- string iteration --"); string_iteration(); idomatic_string_iteration(); print!("\n -- string iteration adapters --"); string_adapters(); print!("\n\n -- byte array iteration --"); define_and_iterate_byte_array(); idiomatic_define_and_iterate_byte_array(); print!("\n\n That's all Folks!\n"); } Rust supplies the usual looping constructs for and loop. It is flexible enough to iterate over a wide variety of types that own data collections with different structures. It does this by supporting iterators in its loop and for blocks. An iterator is a machine, provided by the collection, smart enough to step through the collection's members. -- demonstrate iteration -- -- string iteration -- utf8 characters from "a test string": a t e s t s t r i n g test_string: "a test string" idiomatic utf8 chars from "another test string": a n o t h e r t e s t s t r i n g at index 1 char of "another test string" is n test_string: "another test string" -- string iteration adapters -- "abc123" is alphabetic false "abc123" is alphanumeric true "abc123" is numeric false "abc123" is ascii true third and fourth chars of "abc123" are: c1 numeric chars of "abc123" are: "123" -- byte array iteration -- bytes from byte array: [1, 2, 3, 4, 5] idiomatic bytes from byte array: length of byte slice: 5 [5, 4, 3, 2, 1] printing array with implicit iteration: [5, 4, 3, 2, 1] That's all Folks!
Data Structures // HelloWorld::main.rs fn main() { println!("Hello, world!"); }
Functions // HelloWorld::main.rs fn main() { println!("Hello, world!"); }
Generic functions // HelloWorld::main.rs fn main() { println!("Hello, world!"); }
Structs // HelloWorld::main.rs fn main() { println!("Hello, world!"); }
Generic structs // HelloWorld::main.rs fn main() { println!("Hello, world!"); }
Basic DIP // HelloWorld::main.rs fn main() { println!("Hello, world!"); }
Generic DIP // HelloWorld::main.rs fn main() { println!("Hello, world!"); }
  Next Prev Pages Sections About Keys