struct assembles several values of assorted types together into a single value so you can deal with them as a unit. Rust has three kind of struct types, named-field
, tuple-like
, unit-like
, which differ in how you refer to their components: - named-field
: gives a name to each component
tuple-like
: Identifies them by the order in which they appearunit-like
: Named-Field Structs
gives a name to each component and struct type looks like this
/// A rectangle of eight-bit grayscale pixels.
struct GrayscaleMap {
pixels: Vec<u8>,
size: (usize, usize),
}
Tuple-like Structs
resembles a tuple. The values held by a tuple-like struct are called elements
, just as the values of a tuple are.
struct Bounds(usize, usize);
let image_bounds = Bounds(1024, 768);
The expression Bounds(1024, 768)
work like function defining the type also implicitly defines a function:
fn Bounds(elem0: usize, elem1: usize) -> Bounds { ... }
Tuple-like
structs are good for newtypes like this:
struct Ascii(Vec<u8>);
In memory, both named-field
and tuple-like
structs are the same thing: a collection of values, of possibly mixed types, laid out in a particular way in memory. For example, earlier in the chapter we defined this struct:
struct GrayscaleMap {
pixels: Vec<u8>,
size: (usize, usize),
}
Rust embeds pixels and size directly in the GrayscaleMap value. Only the heap-allocated buffer owned by the pixels
vector remains in its own block.
An impl block is simply a collection of fn
definitions, each of which becomes a method.
#[derive(Debug)]
struct Pixel {
red: u8,
green: u8,
blue: u8,
}
impl Pixel {
fn rev(&mut self) {
self.red = u8::MAX - self.red;
self.green = u8::MAX - self.green;
self.blue = u8::MAX - self.blue;
}
fn show(&self) {
println!("{:?}", &self);
}
}
#[test]
fn test_impl() {
let mut pix = Pixel {
red: 130,
green: 40,
blue: 210,
};
pix.show(); // Pixel { red: 130, green: 40, blue: 210 }
pix.rev();
pix.show(); // Pixel { red: 125, green: 215, blue: 45 }
}
A method’s self argument can also be a Box<Self>
, Rc<Self>
, or Arc<Self>
. Such a method can only be called on a value of the given pointer type. Those types of arguments can manage ownership of the pointer. Suppose we have a tree of nodes like this:
#[derive(Debug)]
struct Node<'a> {
tag: &'a str,
children: Vec<Rc<Node<'a>>>,
}
impl <'a>Node<'a> {
fn new(tag: &str) -> Node {
Node {
tag: tag,
children: vec![],
}
}
const ROOT: Node<'a> = Node{ tag: "Root", children: vec![] };
}
Let's add a method that appends it to some other Node’s children:
fn append_to(self, parent: &mut Node<'a>) {
parent.children.push(Rc::new(self));
}
This method calls Rc::lnew to allocate a fresh heap location and move itself into it.
fn append_to(self: Rc<Self>, parent: &mut Node<'a>) {
parent.children.push(self);
}
This passes ownership of shared_node to the method: no reference counts are adjusted, and there’s certainly no new allocation
Finally, we can use this struct like this:
let mut parent = Node::new("parent");
let owned = Node::new("owned directly");
Rc::new(owned).append_to(&mut parent);
The caller is able to minimize allocation and reference-counting activity given its own needs.
Rc::new
to allocate heap space and move the Node into it. Since parent will insist on referring to its children via Rc<Node>
pointers, this was going to be necessary eventually.As the name implies, associated consts are constant values. They’re often used to specify commonly used values of a type.
impl <'a>Node<'a> {
const ROOT: Node<'a> = Node{ tag: "Root", children: vec![] };
}
you can use them without referring to another instance of Node
let mut root = Node::ROOT;
Rust structs can be generic, meaning that their definition is a template into which you can plug whatever types you like.
pub struct MyQueue<T> {
queue: Vec<T>,
}
impl<T> MyQueue<T> {
pub fn new() -> MyQueue<T> {
MyQueue { queue: vec![] }
}
pub fn push(&mut self, t: T) {
self.queue.push(t);
}
}
You can constrain a type to ensure it implements a specific trait. This allows you to use the trait's functions, such as to_string
.
impl<T: ToString> MyQueue<T> {
pub fn new() -> MyQueue<T> {
MyQueue { queue: vec![] }
}
pub fn push(&mut self, t: T) {
self.queue.push(t);
}
pub fn to_string_vec(&mut self) {
self.queue.iter_mut().map(|element| {element.to_string();});
}
}
If a struct type contains references, you must name those references’ lifetimes.
struct Extrema<'elt> {
greatest: &'elt i32,
least: &'elt i32,
}
As you see, lifetime parameters position same as Generic. If you use both of them, you should write the code like:
pub struct MyQueue<'a, T> {
queue: Vec<&'a T>,
}
impl<'a, T: ToString> MyQueue<'a, T> {
pub fn new() -> MyQueue<'a, T> {
MyQueue { queue: vec![] }
}
pub fn push(&mut self, t: &'a T) {
self.queue.push(t);
}
pub fn to_string_vec(&mut self) {
self.queue.iter_mut().map(|element| {
element.to_string();
});
}
}
How can we define struct as "partially mutable"? We can make it with Cell
or RefCell
. You can get a information about Cell
and RefCell
at this page.
struct MySystem {
systems: Vec<&'static str>,
systems_with_cell: Cell<Vec<&'static str>>,
}
let my_system = MySystem{ systems: vec![], systems_with_cell: Cell::new(vec![])};
my_system.systems = vec!["Notification"];
my_system.systems_with_cell.set(vec!["Notification"]);