Rust as a safe language: Pattern Matching, Handling Results/Errors, Options and Simple macros
Pierre Cochard, Tanguy Risset
- Introduction
- Pattern matching in Rust
- The
Result
enum type - The
Option
enum type - Simple macros with
macro_rules!
Introduction
TODO
Pattern matching in Rust
Pattern matching is the act of checking a given sequence of tokens or expressions for the presence of one or more specific patterns. The concept is implemented in many programming languages (Rust, Haskell, Swift, etc.) and tools, for various purposes, such as: regular expressions, search and replace features, etc.
In Rust, patterns and pattern matching constitute a specific syntax, that is used in different places in the language (match
statements, if let
expressions, function parameters, simple macros, etc.)
Course: match
statements
The primary, and most explicit, use of pattern matching in Rust is done through the match
statement, which can be perceived as the Rust-equivalent of a C switch
, but with additional features. Its syntax is also a little bit different. For instance, let's take a look at this simple C program:
// C basic switch case:
enum Colors {
Red, Blue, Green, Yellow, Orange
};
bool match_color_orange(Colors color) {
switch (color) {
case Orange: {
printf("Orange!\n");
return true;
}
case Red:
case Blue: {
printf("Not orange :(\n"));
return false
}
default: {
printf("Still not orange\n");
return false;
}
}
}
In Rust, we would have the following equivalent:
#![allow(unused)] fn main() { enum Colors { Red, Blue, Green, Yellow, Orange } fn match_color_orange(color: Colors) -> bool { match color { // 'case' statements are replaced by the // 'PATTERN => EXPRESSION' syntax: Colors::Orange => { println!("Orange!"); true } // We use '|' operators here, instead of having // multiple 'case' statements: Colors::Red | Colors::Blue => { println!("Not orange :("); false } // Anything else (equivalent to 'default'): _ => { println!("Still not orange..."); false } } } }
Once a pattern mach is found, the corresponding instruction are executed and the match instruction terminates (it does not check for other matching patterns, the first matching pattern is choosen).
match
statements can be directly bound to variables:
#![allow(unused)] fn main() { enum Colors { Red, Blue, Green, Yellow, Orange } let color = Colors::Red; let is_color_warm = match color { Colors::Orange => true, Colors::Red => true, Colors::Yellow => true, _ => false }; }
Matching ranges is also supported, for instance:
#![allow(unused)] fn main() { fn match_number(number: i32) { match number { 50..=99 => println!("Between 50 and 99"), 100..=1000 => println!("Between 100 and 1000"), _ => println!("Other value") } } }
And, as a matter of fact, any other type of expression can be matched! from string types:
#![allow(unused)] fn main() { fn match_str(s: &'static str) { match s { "Orange" => println!("Orange!"), "Yellow" => println!("Not orange"), _ => println!("Something else...") } } }
to other kinds of collections:
#![allow(unused)] fn main() { fn match_tup(tup: (i32, i32)) { match tup { (0, 0) => println!("Zeroes!"), (1, 1) => println!("Ones"), _ => println!("Something else...") } } match_tup((1, 1)); match_tup((0, 1)); fn match_array(arr: [u8; 3]) { match arr { [0, 1, 2] => println!("Array match!"), _ => println!("No match") } } match_array([0, 1, 2]); match_array([4, 5, 6]); fn match_slice(sl: &[i32]) { match sl { &[0, 1, 2] => println!("Slice matches!"), _ => println!("No match") } } match_slice(&[0, 1, 2]); }
Write a match
statement which applies to anyi32
number. It should only have the two following patterns:
- The value is below
100
(including negative numbers);- The value is equal or higher than
100
.
Course: match
statements: "flexible" patterns
As we saw earlier, the match
statement can test any kind of value, and it also extends to custom and composite types, including struct
instances. A custom struct
can be indeed either matched by its contents in a very precise manner:
#![allow(unused)] fn main() { struct Point { x: isize, y: isize } let point = Point {x: 0, y: 100}; match point { // Only match Point if its 'x' member is equal to 0 // and 'y' is equal to 100: Point {x: 0, y: 100} => println!("Match!"), _ => println!("No match!") } }
Or, in a more flexible way, using, for instance, ranges
for its member values:
#![allow(unused)] fn main() { struct Point { x: isize, y: isize } let point = Point {x: 25, y: 100}; match point { // Only match if 'x' is between 0 and 100, // and 'y' is between 50 and 100 Point {x: 0..=100, y: 50..=100} => println!("Match!"), _ => println!("No match!") } }
Finally, the _ =>
expression can be extended to any kind of value (or field value) that we want to ignore. This can also be done using the ..
syntax, which will ignore all the following values or field values:
#![allow(unused)] fn main() { struct Point { x: isize, y: isize } let p = Point {x: 0, y: 100}; match p { // Only match if 'x' is between 0 and 100, // and ignore the 'y' field: Point {x: 0..=100, y: _} => println!("Match!"), // Only match if 'x' is between 101 and 1000, // Similarly, the '..' syntax will ignore all the struct fields after 'x': Point {x: 101..=1000, ..} => println!("Match!"), _ => println!("No match!") } }
Note: for compound/collection types, the ..
syntax may be followed by other patterns:
#![allow(unused)] fn main() { let tup = (0, 1, 2, 3, 4); match tup { // The first element of the tuple should be '0', and the last should be '4', // we ignore the values in-between: (0, .., 4) => println!("Match!"), _ => println!("No match") } }
Implement a match
statement on a&[i32]
slice. It should match all of the following patterns:
- The slice's first value should be
0
;- The slice's second value should be either
10
or20
;- The slice's final value should be
100
;- The slice can have an arbitrary size.
You can use the following assert!
statements to test your code:
#![allow(unused)] fn main() { fn match_slice(s: &[i32]) -> bool; assert_eq!(match_slice(&[1, 20, 20, 30, 100]), false); assert_eq!(match_slice(&[0, 5, 20, 30, 100]), false); assert_eq!(match_slice(&[0, 10, 20, 30, 99]), false); assert!(match_slice(&[0, 10, 20, 30, 40, 100])); assert!(match_slice(&[0, 20, 20, 30, 50, 60, 70, 80, 90, 100])); }
The Result
enum type
Course: Returning a Result
from a function
To handle and propagate runtime errors, Rust relies on a simple but efficient mechanism based on an enum
: the Result<T, E>
enum, which is defined as:
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E) } }
The templated T
and E
types have no trait implementation predicate whatsoever, they could be anything, for instance:
#![allow(unused)] fn main() { // This function returns a u8 slice if there is no error, a Vec<f32> if there is. // This is probably not very useful, but it's still perfectly valid code: fn my_function(i: i32) -> Result<&[u8], Vec<f32>> {...} // This too (empty tuple type for both): fn my_function(i: i32) -> Result<(),()> { Ok(()) } }
The unwrap()
method can be used to extract the Ok
argument from a Result<...>
. This will be explained in more detail further, but you will need it to test your function:
let u = my_function(3).unwrap();
Write a function called positive()
that takes ani32
as argument and checks whether it is (strictly) positive. It should return the same argument value if it is positive, or aString
with an error message if the argument is negative or equal to zero.
Course: propagating Errors
An Error in Rust can be propagated down the call stack by using the ?
syntax:
#![allow(unused)] fn main() { fn my_function(i: i32) -> Result<i32, ()> {...} fn my_other_function() -> Result<i32, ()> { // Append the '?' operator right after the function call: let mut i = my_function(1)?; // Do something with 'i': i += 1; // Return an 'Ok' result with the modified 'i' value: Ok(i) } }
Here, the my_function(1)?
function call means:
- if the result enum value is
Err
(an error), then propagate the error now, by returning the sameErr
frommy_function()
, otherwise, continue with the rest of the code.
This code could also be implemented with an equivalent match
statement, but is a bit more verbose:
#![allow(unused)] fn main() { fn my_other_function() -> Result<i32, ()> { let mut i = match my_function(1) { Ok(i) => i, Err() => return Err(()) }; i += 1; Ok(i) } }
Implement the same mechanism for the previous positive(i: i32)
example, using the?
syntax, and test it with both positive and negative values in a new function with the same return type, in order to see what happens.
// Your 'positive' function: fn positive(i: i32) -> Result<i32, String> {...} // Define a new function, and call 'positive(...)' from here: fn check_positive() -> Result<i32, String> { ... // <- test positive & negative values here using the '?' syntax } // Check the return type from main: fn main() { println!("{:?}", check_positive()); }
Course: returning a Result
from the main()
function
In general, returning with or without error from a main
function, such as in the C
or C++
programming languages, is done by returning an integer exit code (0
for success, error otherwise):
int main(void) {
// No error, return 0:
return 0;
}
In Rust, the main()
function has no return type by default, and returning a i32
is not accepted by the compiler. For instance, the following code is invalid:
fn main() -> i32 { 0 }
In this case, the compiler prints the following:
error[E0277]: `main` has invalid return type `i32`
--> src/main.rs:3:14
|
3 | fn main() -> i32 {
| ^^^ `main` can only return types that implement `Termination`
|
= help: consider using `()`, or a `Result`
The Termination
trait documentation indeed indicates that only the following types are valid:
#![allow(unused)] fn main() { impl Termination for Infallible; impl Termination for !; impl Termination for (); impl Termination for ExitCode; impl<T: Termination, E: Debug> Termination for Result<T, E>; }
Therefore, we can see that propagating a Result
down to main()
is possible, but is still a bit of a specific case. The type T
held by the Ok
enum value must implement the Termination
trait, and the type held by the Err
enum value must implement the Debug
trait. For instance, the following still does not work because i32
does not implement the Termination
trait:
fn main() -> Result<i32, String> { Ok(0) }
But the following works:
// Using an empty tuple as the 'Ok' result: fn main() -> Result<(), String> { Ok(()) } // Or using the ExitCode type: use std::process::ExitCode; fn main() -> Result<ExitCode, String> { Ok(ExitCode::from(0)) }
Result
type in the main()
function, propagate the Result
of our positive()
function down.
fn positive(i: i32) -> Result<i32, String> {...} fn check_positive() -> Result<i32, String> {...} // Use a valid Result type here: fn main() -> Result<?, ?>{ // Call the 'check_positive' function here, and propagate its Result as the main() return type: }
Course: Handling Result
types immediately
.unwrap()
, .expect()
In some cases - when propagating an error is not possible, or unconvenient - dealing immediately with a Result
type is preferrable. This is why certain methods, such as .unwrap()
or .expect()
are natively implemented, and quite commonly used:
- The
.unwrap()
method, for instance, will induce apanic!
call and will exit the program immediately when encountering an error. Otherwise it will return theOk
value safely:
#![allow(unused)] fn main() { // Get the current working directory: let dir: std::path::PathBuf = std::env::current_dir().unwrap(); println!("{:?}", dir); }
- The
.expect()
method is really similar, but will allow the user to print a custom&str
message on error, which will be prepended to the actual display of theErr
contents:
#![allow(unused)] fn main() { fn my_function() -> Result<(), i32> { Err(1) } my_function().expect("Error! Now exiting program with error code"); }
- Other similar helper methods also exist, with different behaviors:
.unwrap_or(other: T)
returns the valueother
in case of anErr
.unwrap_or_default()
returns the type's default value in case of anErr
.unwrap_or_else(func: Fn)
executes a custom function in case of anErr
- etc.
panic!
, assert!
and other macros
In addition to Result
types, other simple tools, in the form of macros, are provided:
- The
panic!(msg)
macro interrupts the program immediately and prints a custom error message:
fn main() { use std::io; // Read input from stdin: let mut buffer = String::new(); println!("Please enter password:"); io::stdin().read_line(&mut buffer).unwrap(); // Panic if password is not long enough: if buffer.len() < 8 { panic!("Password should be at least 8 characters"); } else { println!("{buffer}"); } }
- The
assert!(bool)
,assert_eq!(lhs, rhs)
, andassert_ne!(lhs, rhs)
, verify a boolean statement or check equality between two elements:
fn main() { use std::io; // Read input from stdin: let mut buffer = String::new(); println!("Please enter password:"); io::stdin().read_line(&mut buffer).unwrap(); // We assert that the password is at least 8 characters // This will cause a 'panic' if the assertion is false: assert!(buffer.len() >= 8, "Password should be at least 8 characters"); // Here, we forbidden choosing 'password' as a password: assert_ne!(buffer.trim(), "password", "Choosing 'password' as password is unsafe."); }
Using Result
, and all the previous examples, create a program which parses a password, with the following rules:
- Password must:
- be at least 8 characters long;
- should contain at least one of these special characters:
!
,?
or_
;- should contain at least one number;
- should not contain any whitespace.
- A specific error message should be displayed for each rule.
Note: in order to verify your program, you can implement a
#[test]
function, such as:
#![allow(unused)] fn main() { #[test] fn password_test() { let pwd0 = String::from("pass"); let pwd1 = String::from("password"); let pwd2 = String::from("password!"); let pwd3 = String::from("pass word!"); let pwd4 = String::from("password!1"); assert!(parse_password(&pwd0).is_err()); assert!(parse_password(&pwd1).is_err()); assert!(parse_password(&pwd2).is_err()); assert!(parse_password(&pwd3).is_err()); assert!(parse_password(&pwd4).is_ok()); } }
and then run
cargo test
on your program.
The Option
enum type
The Option
enum in Rust is somewhat similar to the Result
enum, but its main purpose is to indicate the presence or absence of a value, rather than an error. It has the following definition:
#![allow(unused)] fn main() { pub enum Option<T> { None, Some(T) } }
As an example, let's suppose we want to build a list of people to contact, with various information, such as the contact's name and address, and optionally phone number and/or e-mail, we could for instance define the following struct
:
#![allow(unused)] fn main() { struct MyContact { name: &'static str, address: &'static str, email: Option<&'static str>, phone: Option<&'static str>, } let mut list: Vec<MyContact> = Vec::new(); list.push(MyContact { name: "Marlo Stanfield", address: "2601 E Baltimore St, Baltimore, MD 21224", email: None, phone: Some("+1 410-915-0909") }); }
By doing this, we can then take advantage of the Option
enum and pattern matching to decide of the best way to contact each person in the list:
#![allow(unused)] fn main() { for contact in list { match (contact.email, contact.phone) { // Both e-mail and phone are available: (Some(email), Some(phone)) => { if is_email_correct(email) { contact_by_email(email); } else { contact_by_phone(phone); } } // Only e-mail is available: (Some(email), None) => { contact_by_email(email); } // Only phone is available: (None, Some(phone)) => { contact_by_phone(phone); } // Neither phone nor email: (None, None) => { send_mail_to_address(contact.address); } } } }
As for the Result
type, Option
can be checked and handled immediately using the same .unwrap()
, .expect()
methods.
#![allow(unused)] fn main() { fn money_left() -> Option<i32>; money_left().expect("No money left :("); }
Using the previous contact list example, implement a small database of books that would be used by a library. It should have a search_book(...)
function which searches for a specific book using its name and/or the name of the author (we assume here that there's only one book per author). The function should return a reference to theBook
object if it has been found, orNone
otherwise.
#![allow(unused)] fn main() { struct Book { name: String, author: String, } #[derive(Default)] struct LibraryDatabase { books: Vec<Book> } impl LibraryDatabase { // The function to implement: fn search_book(&self, name: Option<&'static str>, author: Option<&'static str> ) -> Option<&Book> {...} } }
You can use the following #[test]
function to verify your code:
#![allow(unused)] fn main() { #[test] fn test() { let mut database = LibraryDatabase::default(); database.books.push(Book { name: String::from("Peter Pan"), author: String::from("Barrie")}); assert!(database.search_book(Some("Peter Pan"), None).is_some()); assert!(database.search_book(None, Some("Barrie")).is_some()); assert!(database.search_book(None, None).is_none()); assert!(database.search_book(Some("Barrie"), None).is_none()); assert!(database.search_book(None, Some("Peter Pan")).is_none()); assert!(database.search_book(Some("Alice in Wonderland"), Some("Barrie")).is_none()); assert!(database.search_book(Some("Peter Pan"), Some("Lewis Carroll")).is_none()); } }
Simple macros with macro_rules!
Unlike other programming languages, such as C
or C++
, Rust's macro system is based on abstract syntax trees (AST), instead of string preprocessing, which makes them a bit more complex to use, but also more reliable and powerful. macros are expanded before the compiler interprets the meaning of the code. The difference between a macro and a function is that macro definitions are more complex than function definitions because you’re writing Rust code that writes Rust code. Due to this indirection, macro definitions are generally more difficult to read, understand, and maintain than function definitions.
Throughout this course, we have already encoutered a few of them, including vec!
, panic!
, assert!
, and of course println!
. These macros are defined by the macro!(...)
syntax (don't forget the trailling exclamation mark) and are called simple macros, as opposed to Rust's more complex macro systems, such as attribute macros (for instance the #[test]
function attribute), and derive macros (the #[derive(Debug)]
statement on top of a struct
), which we have already both seen as well.
Course: Basic macro_rules!
usage
Unlike attribute or derive macros, which must be defined in a separate crate, simple macros can be defined anywhere in our code, using the macro_rules!
syntax:
#![allow(unused)] fn main() { macro_rules! hello_world { () => { println!("Hello World!") }; } hello_world!(); }
Here, we defined a macro!
that takes no argument, which is indicated by the ()
statement. Our hello_world!()
macro call will be under the hood replaced by the contents that we defined within the => { ... }
block.
Advantages of using macros
Our hello_world!()
example is of course not very useful, and in fact adds unnecessary noise to a very simple piece of code, but think of the vec!
macro for instance:
#![allow(unused)] fn main() { let v1 = vec![1, 2, 3, 4, 5]; let v2 = vec!(); let v3 = vec![1]; }
Defining the three different vectors by hand would actually mean writing the following code:
#![allow(unused)] fn main() { let v1 = <[_]>::into_vec(Box::new([1, 2, 3, 4, 5])); let v2 = Vec::new<i32>(); let v3 = std::vec::from_elem(1, 1); }
Notice how these three vectors are each time created in a very different way? In this case, the vec!
macro allows defining a more practical and unified way of instantiating a Vec
object, without having to remember all the (sometimes complex) underlying code. Furthermore, as you can see with this example, a macro!
can also accept a variable number of arguments, which is not the case with a Rust function.
The different types of arguments (or fragment specifiers)
As you may already have guessed with our first basic macro example, which uses the =>
operator, macro_rules!
relies on pattern matching to parse its arbitrary number of arguments.
macro_rules!
can parse different kinds of patterns, including:
()
: the empty pattern, which means no argument (our previous example);block
: a block expression, surrounded by{ }
;expr
: any kind of Rust expression;ident
: an identifier (the name of a variable, function, etc.);literal
: a number/string or other kind of litteral;- etc. (see the full list here).
Matching a specific pattern
Let's now try an example with an actual argument. Here, we will use the ident
designator in order to create functions from a simple macro call:
#![allow(unused)] fn main() { macro_rules! define_fn { ($fn_name:ident) => { fn $fn_name() { println!( "This function's name (ident) is: '{}()'.", stringify!($fn_name) ); } } } define_fn!(foo); define_fn!(bar); foo(); bar(); }
Let's break this code piece-by-piece:
#![allow(unused)] fn main() { ($fn_name:ident) => { }
→ Instead of an empty pattern ()
, we use the pattern ($fn_name:ident)
, in which $fn_name
would be the name of the argument, and ident
its type. The dollar sign ($) is used to declare a variable in the macro system that will contain the Rust code matching the pattern.
#![allow(unused)] fn main() { fn $fn_name() { }
→ Within our generated code block, we declare a function with the name taken from our $fn_name
ident argument.
#![allow(unused)] fn main() { println!( "This function's name (ident) is: '{}()'.", stringify!($fn_name) ); }
→ We then define the function's body, with a println!
call, in which we print the ident's name using a utility macro called stringify!
. This very useful macro will transform our $fn_name
identifier into a &'static str
object;
What would be the generated code for the define_fn!(foo)
macro call?
Create a similar macro, but this time the generated code should define a struct
and itsimpl
block like the following:
#![allow(unused)] fn main() { // All of this code should be generated by our new macro, // but the name 'Foo' should be made variable: struct Foo { print: &'static str } impl Foo { fn new() -> Foo { Foo { print: "Foo" } } } }
#![allow(unused)] fn main() { // The macro to implement: macro_rules! define_struct { ... } // Use the following to verify that the macro is correct: define_struct!(Foo); let bar = Foo::new(); assert_eq!(bar.print, "Foo"); }
Course: pattern overloading
macro_rules!
definitions can accept an arbitrary number of patterns, in a very simple way. Let's try it out on our define_fn!
macro. We will add another pattern allowing to add arbitrary code expressions to the created fn
:
#![allow(unused)] fn main() { macro_rules! define_fn { ($fn_name:ident) => { fn $fn_name() { println!( "This function's name (ident) is: '{}()'.", stringify!($fn_name) ); } }; // <-- pattern blocks must end with a semicolon if they're followed by other blocks // Our new pattern: ($fn_name:ident, $additional_code:expr) => { fn $fn_name() { println!( "This function's name (ident) is: '{}()'.", stringify!($fn_name) ); // Append the additional code 'expr' at the end of the defined fn: $additional_code } } } define_fn!(foo); define_fn!(bar, println!("Additional code")); foo(); bar(); }
Here, we added the ($fn_name:ident, $additional_code:expr)
pattern, which is composed of two arguments: the same ident
argument, followed by an expr
argument, which can be any valid Rust expression. The two arguments are separated by a comma ,
but the choice of a comma is completely arbitrary, it could be (almost) any symbol.
In our previous example, try replacing the ,
symbol between$fn_name:ident
and$additional_code:expr
with another one. Then, call thedefine_fn!
macro with two arguments separated by the same new symbol, and see what it does.
Course: pattern matching for macro_rules!
Pattern matching for macro_rules!
is quite different from pattern matching used in the match
keyword. Macros can also easily deal with pattern repetition by using a special syntax, which resembles the one used for regular expressions. In particuler, the usual operators of regular expressions can be used: '_', '+'
or '*'
(one object, a repetition of objects -- at least one, a repetition of objects - possibly 0). In the following example, we want to replace the std::cmp::max()
function to take an arbitrary number of arguments:
#![allow(unused)] fn main() { let mut max = std::cmp::max(1, 2); max = std::cmp::max(max, 3); max = std::cmp::max(max, 4); max = std::cmp::max(max, 5); }
In this case, having a macro like the following could prove useful, and would lighten the code a lot:
#![allow(unused)] fn main() { max!(1, 2, 3, 4, 5, 6*7, 3*4); }
The way to do this is to use the $(...),+
syntax, as follows:
#![allow(unused)] fn main() { macro_rules! max { // Only one argument 'x', return 'x': ($x:expr) => {$x}; // At least two arguments, // - 'x' being the first, // - 'y' being one or more additional argument(s), // which is defined by the '$(...),+' syntax: ($x:expr, $($y:expr),+) => { // We recursively call 'max!' on the tail 'y' std::cmp::max($x, max!($($y),+)) } } }
The +
in the $($y:expr),+
syntax means one or more instances of the ($y)
expression, separated by a comma ,
.
Note: The
*
symbol also exists, and means zero or more instances of the pattern.
Now, if we were to call the max!
macro the following way, we would only match the first pattern ($x:expr) => {$x}
:
#![allow(unused)] fn main() { max!(1); }
- With two arguments, we would match the second pattern
($x:expr, $($y:expr),+)
with a single additional argument:
#![allow(unused)] fn main() { max!(1, 2); // expands to: std::cmp::max(1, max!(2)); // expands to: std::cmp::max(1, 2); }
- And with more arguments recursively:
#![allow(unused)] fn main() { max!(1, 2, 3); // expands to: std::cmp::max(1, max!(2, 3)); // expands to: std::cmp::max(1, std::cmp::max(2, max!(3))); // expands to: std::cmp::max(1, std::cmp::max(2, 3)); }
As a summary:
$var
captures a value in a pattern.$var:ident
specifies a type (could beident
,expr
,ty
, etc.).$( $(var:pat),*
or$( $(var:pat),+
captures repetitive sequences separated by commas.$var
is replaced during macro expansion.
Bonus: Transform our previous define_struct!
example into a more elaborated macro. It should now have the following interface:
#![allow(unused)] fn main() { // Define the struct 'Foo': define_struct!( name: Foo, members: { bar: i32 } methods: { fn hello() { println!("hello!") } fn bar(&self) { println!("{}", self.bar); } } ); // Instantiate the struct 'Foo': let f = Foo::default(); // Call its bar method: f.bar(); // Or its static 'hello' method: Foo::hello(); }
Bonus: Add a nested macro_rules!
definition intodefine_struct!
which allows to copy the members and methods of the defined struct into a new different one, such as:
#![allow(unused)] fn main() { // Define Foo struct: define_struct!( name: Foo, members: { bar: i32 } methods: { fn hello() { println!("hello!") } fn bar(&self) { println!("{}", self.bar); } } ); // The define_struct! macro should define a new 'Foo!' macro, // which allows copying the members and methods of 'Foo' into another new struct: Foo!(FooCopy); let c = FooCopy::default(); c.bar(); FooCopy::hello(); }