Generic Functions:
Generic functions accept arguments, one or more of which depend on an
unspecified type. Generic types are resolved to concrete types when the
invoking code is compiled.
Usually a generic argument is constrained by one or more
traits. In the case of the generic function, show below, T is constrained
to support the Debug trait - Debug specifies a display format suitable for
debugging applications.
If the invoking argument does not satisfy constraints
imposed by the function, it will fail to compile.
Generic Function
/*------------------------------------------
Show slice as stack of rows
- span is number of items in row
- width is size of field holding item
*/
fn show_fold<T:Debug>(
t:&[T], span:usize, width:usize
) {
print!("\n ");
let mut count = 0;
for item in t {
count = count + 1;
print!("{:wd$?}", item, wd = width);
if count == span {
print!("\n ");
count = 0;
}
}
}
Using Code:
fn demo_array_int() -> Option<()> {
// code elided
// - includes function returning Option
let arr =
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
show_fold(&arr, 5, 4);
Some(())
}
Output:
// output elided
0 1 2 3 4
5 6 7 8 9
10 11
In the example shown in the dropdown, below, you will find a generic
function used to display collection contents to the console.
It displays a comma separated list of values from a collection of arbitrary
type that satisfies the constraints: the collection must define the IntoIterator
trait, to support iterating through the collection, it must support the clone
type to avoid consuming the collection, and the collection and its items must
satisfy the Debug trait.
We will discuss traits in some detail in the Traits Bite.
Generic Function that displays collections: show_coll:
collections::main.rs
use std::fmt::Debug;
use std::any::*;
/*-----------------------------------------------------------
Display comma separated list of collection items
- shows how to build function accepting iterable
collections
- returns Option::None if collection is empty
*/
fn show_coll<C>(c:&C) -> Option<()>
where C:IntoIterator + Clone + Debug, C::Item: Debug {
let mut iter = c.clone().into_iter();
/*-------------------------------------------
returns Option if no next element using
? try operator
*/
print!("\n {:?}", iter.next()?);
/*-- breaks when no next element --*/
for item in iter {
print!(", {:?}", &item);
}
Some(())
}
Using Code:
fn demo_array_int() -> Option<()> {
print!("\n === demo_array_int ===");
let mut arr : [i32; 5] = [0; 5];
show_coll(&&arr)?;
arr = [1, 2, 3, 4, 5];
show_coll(&&arr)?;
let arr_slice = &arr[..];
show_coll(&arr_slice);
arr[1] = -1;
show_coll(&&arr)?;
let arr =
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
show_fold(&arr, 5, 4);
Some(())
}
fn demo_vec_int() {
print!("\n === demo_vec_int ===");
let mut vec = vec![1, 2, 3, 4, 5];
show_coll(&vec);
print!("\n -- assign vectors --");
vec = vec![5, 4, 3, 2, 1];
show_coll(&vec);
// rest of code elided
}
Output:
=== demo_array_int ===
0, 0, 0, 0, 0
1, 2, 3, 4, 5
1, 2, 3, 4, 5
1, -1, 3, 4, 5
0 1 2 3 4
5 6 7 8 9
10 11
=== demo_vec_int ===
1, 2, 3, 4, 5
-- assign vectors --
5, 4, 3, 2, 1
// rest of output elided
In the dropdown, below, you will find two functions used to display
information about the value and type of a generic parameter. These can be useful
when you are investigating a third-party library or trying to get your own code to compile
and run correctly.
The first of the two functions uses 'a to denote a lifetime. Rust tracks the lifetimes
of all references to be sure they do not outlive their referends. The compiler's
borrow checker occasionally needs help to determine a lifetime, and that is done with this
notation. We will discuss this more in the lifetime Bite.
Examples of Generic Display Functions:
collections::main.rs
use std::any::*;
/*-----------------------------------------------------------
Display type name of generic input argument
- returns reference, &str, to literal string
- requires lifetime annotation, 'a
- argument name _t tells compiler we don't intend to
use the argument, just its type T
*/
fn show_type<'a, T:Debug>(_t:T) -> &'a str where T:Debug {
type_name::<T>()
}
use std::fmt::Debug;
/*-----------------------------------------------------------
Display argument name, argument value, and type of
generic input, t:T
*/
fn show_type_value<T:Debug>(name:&str, t:&T) {
print!("\n value of {} = {:?}", name, t);
print!("\n type is: {}", show_type(t));
}
Using Code:
fn demo_struct() {
print!("\n -- demo_struct --");
#[derive(Debug)]
struct S { a:i8, b:String, c:f64 }
let s: S =
S {
a:42, b:String::from("String"),
c:3.1415927
};
assert_eq!(s.a, 42);
show_type_value("s", &s);
print!(
"\n s.a = {}, s.b = {}, s.c = {}",
s.a, s.b, s.c
);
println!();
#[derive(Debug)]
struct T(i8, String, f64);
let t:T =
T(42, String::from("String"), 3.1415927);
assert_eq!(t.0, 42);
show_type_value("t", &t);
print!(
"\n t.0 = {}, t.1 = {}, t.2 = {}",
t.0, t.1, t.2
);
println!();
// code elided
}
Output:
-- demo_struct --
value of s =
S { a: 42, b: "String", c: 3.1415927 }
type is: &collections::demo_struct::S
s.a = 42, s.b = String, s.c = 3.1415927
value of t = T(42, "String", 3.1415927)
type is: &collections::demo_struct::T
t.0 = 42, t.1 = String, t.2 = 3.1415927
// output elided
In the last dropdown for this section you will find basic and more advanced topics:
- pass and return by value and reference
- functions that take and return function arguments
- dynamic dispatch - compared to static dispatch
- local functions
Examples: Summary of function topics:
functions::main.rs
/////////////////////////////////////////////////////////////
// functions_and_methods::main.rs - functions Bite demos //
// //
// Jim Fawcett, https://JimFawcett.github.com, 19 Jun 2020 //
/////////////////////////////////////////////////////////////
// https://stackoverflow.com/questions/36390665
// /how-do-you-pass-a-rust-function-as-a-parameter
// https://joshleeb.com/blog/rust-traits-trait-objects/
/*-----------------------------------------------------
pass arguments by value
- passing by value results in Move for non-copy types
- we say that the s and t arguments are consumed
*/
fn f(mut s:String, t:String) -> String {
s.push(' ');
s.push_str(&t);
s // value of last expression is returned
}
/*-----------------------------------------------------
pass arguments by reference
- passing s and t does not consume their referends
*/
fn g(s:&mut String, t:&str) -> String {
s.push(' ');
s.push_str(t);
s.to_string()
}
/*-----------------------------------------------------
h_in_1 accepts function argument via static dispatch
- F: Fn ... specifies accepted function signatures
*/
fn h_in_1<F: Fn(i32) -> String>(i:i32, f: F) -> String {
f(i)
}
/*-----------------------------------------------------
h_in_2 accepts function argument via dynamic dispatch
- Fn ... specifies accepted function signatures
*/
fn h_in_2(i:i32, f: &dyn Fn(i32) -> String) -> String {
f(i)
}
/*-----------------------------------------------------
function passed to, and returned from demonstration
functions, below
*/
fn test_function(i:i32) -> String {
i.to_string()
}
/*-----------------------------------------------------
static dispatch of returned function
- uses impl to specify that return satisfies Fn ...
*/
fn h_out_1() -> impl Fn(i32) -> String {
&test_function
}
/*-----------------------------------------------------
dynamic dispatch of returned function
- uses lifetime annotation 'a
- &dyn to include vtable
*/
fn h_out_2<'a>() -> &'a dyn Fn(i32) -> String {
&test_function
}
Using Code:
fn main() {
print!(
"\n -- passing function args by value --"
);
let s1 = String::from("a string");
print!("\n s1 = {:?}", s1);
let s2 = String::from("and more");
print!("\n s2 = {:?}", s2);
let s3 = f(s1, s2);
print!("\n s3 = {:?}", s3);
// print!("\n s2 = {:?}", s2);
print!("\n can't print s2, it's been moved");
// print!("\n s1 = {:?}", s1);
print!("\n can't print s1, it's been moved");
println!();
print!(
"\n -- passing function args by reference --"
);
let mut s1 = String::from("a refreshed string");
let s2 = "and a new more";
let s3 = g(&mut s1, s2);
print!("\n s3 = {:?}", s3);
print!("\n s2 = {:?}", s2);
print!("\n s1 = {:?}", s1);
print!(
"\n note - s1 has been changed as side-effect"
);
println!();
print!("\n -- passing function as argument --");
/*-- static dispatch --*/
let s = h_in_1(42, &test_function);
print!("\n s = {}", s);
/*-- dynamic dispatch --*/
let s = h_in_2(42, &test_function);
print!("\n s = {}", s);
println!();
print!("\n -- function returning function --");
let s = h_out_1()(42); // static dispatch
print!("\n s = {}", s);
let s = h_out_2()(42); // dynamic dispatch
print!("\n s = {}", s);
println!();
print!(
"\n -- defining function inside function --"
);
fn whooaaa() {
print!("\n whooaaa - inside main!");
}
whooaaa();
print!("\n\n That's all Folks!\n\n");
}
Output:
cargo run -q
-- passing function arguments by value --
s1 = "a string"
s2 = "and more"
s3 = "a string and more"
can't print s2, it's been moved
can't print s1, it's been moved
-- passing function arguments by reference --
s3 = "a refreshed string and a new more"
s2 = "and a new more"
s1 = "a refreshed string and a new more"
note that s1 has been changed as a side-effect
-- passing function as argument --
s = 42
s = 42
-- function returning function --
s = 42
s = 42
-- defining function inside another function --
whooaaa - inside main!
That's all Folks!
In the next section we discuss closures, sometimes call lambdas. They work a lot like locally
defined functions, although their syntax is quite different, and they can do one thing that
functions can't.