Structs let you create meaningful custom data types by grouping related values together. Instead of passing separate variables such as a server name, port, TLS flag and retry count, you can represent the entire configuration as one type.
Define and instantiate a struct
struct Server { name: String, port: u16, tls: bool, } fn main() { let server = Server { name: String::from("api-01"), port: 8080, tls: true, }; println!("{}:{}", server.name, server.port); }Each field has a name and type. An instance must provide values for all required fields.
Mutable struct instances
let mut server = Server { name: String::from("api-01"), port: 8080, tls: false, }; server.tls = true;The complete instance is mutable; Rust does not let you mark only a single field as mut in the struct definition.
Field init shorthand
fn build_server(name: String, port: u16) -> Server { Server { name, port, tls: true, } }When parameter/variable names match field names, you can omit name: name.
Struct update syntax
let primary = Server { name: String::from("primary"), port: 443, tls: true, }; let backup = Server { name: String::from("backup"), ..primary };Be careful with ownership: fields that do not implement Copy can be moved from the source instance by update syntax. Here the explicitly replaced name changes how much of primary is moved, while copied scalar fields remain simple values.
Tuple structs
struct Color(u8, u8, u8); struct Point(i32, i32); let orange = Color(255, 120, 0); println!("red={}", orange.0);Tuple structs are useful when a distinct type matters but field names add little value.
Unit-like structs
struct HealthCheck; let check = HealthCheck;Unit-like structs have no fields. They can still implement traits and methods and are useful as marker or behavior types.
Methods with impl
struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let rect = Rectangle { width: 30, height: 20, }; println!("area={}", rect.area()); }A method is defined inside an impl block. Its first parameter is commonly one of &self, &mut self or self.
&self vs &mut self vs self
| Receiver | Meaning | Typical use |
|---|---|---|
| &self | Immutable borrow | Read instance state |
| &mut self | Mutable borrow | Modify instance state |
| self | Take ownership | Consume/transform instance |
Mutable method
impl Server { fn enable_tls(&mut self) { self.tls = true; } }Consuming method
struct Job { id: u64, } impl Job { fn finish(self) { println!("job {} finished", self.id); } }After calling a consuming method, the original instance is no longer available unless the method returns it or returns another owned value.
Associated functions
Functions defined in an impl block that do not take self are associated functions. Constructors commonly use this style:
impl Server { fn new(name: impl Into<String>, port: u16) -> Self { Self { name: name.into(), port, tls: false, } } } let server = Server::new("api", 8080);new is a convention, not a special Rust keyword.
Multiple impl blocks
impl Server { fn new(name: String, port: u16) -> Self { Self { name, port, tls: false } } } impl Server { fn endpoint(&self) -> String { let scheme = if self.tls { "https" } else { "http" }; format!("{scheme}://{}:{}", self.name, self.port) } }Multiple impl blocks are valid and can help organize implementations.
Owned vs borrowed fields
struct User { username: String, }An owned String means the struct owns the username and can live independently of the text used to construct it. Storing references inside structs is possible, but usually requires lifetime relationships. We cover lifetimes later in the learning path.
Derived traits
#[derive(Debug, Clone)] struct Server { name: String, port: u16, tls: bool, } println!("{:?}", server);derive can ask the compiler to generate implementations of supported traits such as Debug, Clone, PartialEq and others when their requirements are met.
Practical example: application configuration
#[derive(Debug)] struct AppConfig { host: String, port: u16, workers: usize, production: bool, } impl AppConfig { fn development() -> Self { Self { host: String::from("127.0.0.1"), port: 8080, workers: 2, production: false, } } fn endpoint(&self) -> String { format!("{}:{}", self.host, self.port) } fn scale(&mut self, workers: usize) { self.workers = workers; } } fn main() { let mut config = AppConfig::development(); config.scale(4); println!("{:?}", config); println!("endpoint={}", config.endpoint()); }Structs vs tuples
Prefer a struct when field names improve meaning. Compare:
let server = ("api", 443, true); // What does server.2 mean? struct Server { name: String, port: u16, tls: bool, } // server.tls is self-explanatoryCommon errors
- Trying to modify a field when the struct variable is not mutable.
- Moving a String field out and then trying to use the original instance as though it were complete.
- Taking self in a method when &self would be sufficient.
- Cloning fields unnecessarily instead of borrowing them.
- Using references as struct fields without understanding the lifetime relationship.
Hands-on mini lab: service configuration
cargo new structs_lab cd structs_lab- Create a Service struct with name , port , enabled and retries .
- Add a new associated function.
- Add an endpoint(&self) method.
- Add enable(&mut self) .
- Derive Debug and print the complete value.
- Create a second instance using struct update syntax and observe which fields move or copy.
cargo fmt cargo clippy cargo test cargo runKey points
- Structs group related values into a custom type.
- impl blocks attach behavior to a type.
- &self reads, &mut self modifies, and self consumes.
- Associated functions are called with Type::function() .
- Ownership rules still apply to fields and struct update syntax.
- Use named fields when they make code easier to understand.
Next chapter
Structs model data with one fixed shape. Rust enums let one type represent multiple valid states. Continue with Chapter 10: Enums, Option and Pattern Matching.
Official reference
See the official Rust Book chapter Using Structs to Structure Related Data.
Join the discussion
0 CommentsAsk questions, add context, or share what worked.
More from Melochip Editorial
Learn what Playwright testing is, why modern QA teams use it, how testers can get started, and the major types...
7 min read · Sep 19, 2026
Learn how Kubernetes Deployments and ReplicaSets work with practical YAML, scaling, self-healing, rolling upda...
7 min read · Sep 17, 2026
Learn Kubernetes security best practices with RBAC, Secrets, ServiceAccounts, Pod securityContext, NetworkPoli...
6 min read · Sep 17, 2026Related articles
Learn what Playwright testing is, why modern QA teams use it, how testers can get started, and the major types...
7 min read · Sep 19, 2026
Learn how Kubernetes Deployments and ReplicaSets work with practical YAML, scaling, self-healing, rolling upda...
7 min read · Sep 17, 2026
Learn Kubernetes security best practices with RBAC, Secrets, ServiceAccounts, Pod securityContext, NetworkPoli...
6 min read · Sep 17, 2026
Learn Kubernetes monitoring and logs with kubectl logs, events, metrics-server, top commands, Prometheus, Graf...
5 min read · Sep 17, 2026Stay in the loop. Get new Melochip articles
Receive Melochip article updates when new articles are available. Explicit opt-in only.
No comments yet. Start the discussion.