Question

Rust Structs with Owned String Fields

ruststring

Question

How can I define a Rust struct whose string fields are owned by the struct, and how can I create instances of it? Please show the correct struct declaration and instantiation examples.

Short Answer

You will learn how to store owned text in a Rust struct with String, initialize its fields, update them safely, and choose between owned String values and borrowed &str references.

Concept

A Rust struct groups related values into one named type. For text that should belong to the struct itself, use the owned String type:

struct User {
    name: String,
    email: String,
}

A String owns its text data. When a User value owns a String, the text remains valid for as long as that User exists. When the User is dropped, Rust automatically drops its String fields too.

This matters because &str is usually a borrowed reference to text owned somewhere else. A struct containing &str fields must specify how long those references remain valid with a lifetime annotation. Using String avoids that lifetime requirement when the struct should own the text.

Use String when:

  • The struct must keep text after the caller's original variable is gone.
  • The text may need to change later.
  • You are building values from user input, JSON, files, databases, or network requests.

Use &str only when the struct intentionally borrows text that is guaranteed to outlive it.

Mental Model

Think of a struct as a backpack.

  • A String field is an item placed inside the backpack. The backpack owns it and carries it wherever it goes.
  • A &str field is a note with directions to an item stored somewhere else. The note is useful only while the original item still exists.

If the struct needs to keep its own copy of text, put a String inside the backpack.

Take Quiz

Syntax and Examples

Use String as the field type, then create String values with String::from(...), .to_string(), or other APIs that produce a String.

struct Book {
    title: String,
    author: String,
    pages: u32,
}

fn main() {
    let book = Book {
        title: String::from("The Rust Book"),
        author: "Rust Project Developers".to_string(),
        pages: 560,
    };

    println!("{} by {}", book.title, book.author);
}

title and author are owned by book. The string literals in the source code have type &'static str, so they are converted into owned String values during construction.

A constructor method can make creation cleaner when a struct has several owned fields:

struct User {
    name: String,
    email: String,
}

impl User {
    fn new(name: impl Into<String>, email: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            email: email.into(),
        }
    }
}

fn main() {
    let user = User::new("Ava", "ava@example.com");
    println!("{}: {}", user.name, user.email);
}

impl Into<String> lets callers pass either string literals such as "Ava" or existing String values.

Step by Step Execution

Consider this program:

struct Profile {
    username: String,
    bio: String,
}

fn main() {
    let username = String::from("mira");

    let profile = Profile {
        username,
        bio: String::from("Learning Rust"),
    };

    println!("{}", profile.username);
}

Step by step:

  1. String::from("mira") allocates an owned string and binds it to username.
  2. The Profile instance is created.
  3. Writing username, uses field-init shorthand because the variable and field have the same name.
  4. The String is moved from the local variable into profile.username.
  5. profile now owns both username and bio.
  6. The println! call borrows profile.username long enough to display it.
  7. At the end of main, profile is dropped, followed by its owned String fields.

After step 4, this would not compile because username was moved:

// println!("{username}"); // error: value borrowed here after move

If you need to keep the original string too, clone it deliberately:

let username = String::from("mira");
let profile = Profile {
    username: username.clone(),
    bio: String::from("Learning Rust"),
};

println!("{username}");

Real World Use Cases

Owned String fields are common when data needs to live independently from the code that created it.

  • API requests: Store a user's name, email address, or message parsed from JSON.
  • Database models: Represent columns such as product names, titles, and descriptions.
  • CLI applications: Keep command-line arguments and configuration values after parsing.
  • File processing: Store lines, paths, and report content read from a file.
  • Domain models: Model entities such as Customer, Order, Article, or GameCharacter with text properties.

For example, deserialized request data is often owned:

struct CreateAccountRequest {
    username: String,
    password: String,
}

The request value can safely move through validation and business-logic functions without depending on the original input buffer.

Take Quiz

Real Codebase Usage

In real Rust projects, structs commonly own data at boundaries: after reading input, parsing a response, or loading database records. Ownership makes it easier to return, store, queue, and pass values between functions safely.

Validate borrowed input, then create an owned model

struct Account {
    username: String,
}

fn create_account(username: &str) -> Result<Account, String> {
    let username = username.trim();

    if username.is_empty() {
        return Err(String::from("Username cannot be empty"));
    }

    Ok(Account {
        username: username.to_owned(),
    })
}

This pattern accepts a borrowed &str so the caller does not need to allocate first. After validation, .to_owned() creates the String that the returned Account owns.

Update a string field

struct Document {
    title: String,
}

fn rename(document: &mut Document, new_title: &str) {
    document.title = new_title.to_owned();
}

Use &mut Document when changing a field. The assignment drops the old String and replaces it with the new owned value.

Accept flexible text in constructors

Constructors often use impl Into<String> to accept both String and &str, while still storing a String internally.

Common Mistakes

Using str instead of String

str is an unsized text type, so it generally cannot be stored directly as a normal struct field.

// Does not compile
struct User {
    name: str,
}

Use String for owned text:

struct User {
    name: String,
}

Assigning a string literal directly to a String field

A string literal is &'static str, not String.

// Does not compile
let user = User {
    name: "Ava",
};

Convert it:

let user = User {
    name: String::from("Ava"),
};

Trying to use a value after moving it into the struct

let name = String::from("Ava");
let user = User { name };

// println!("{name}"); // does not compile: `name` was moved

Access it through user.name, borrow it before moving, or use name.clone() if two independent owners are truly needed.

Borrowing a temporary string

// Do not try to store a reference to temporary generated text.
// The referenced value would be dropped too soon.

If the struct needs generated text, store it as a String.

Adding Clone automatically

Cloning works but may allocate and copy data. Prefer moving a String into the struct when the caller no longer needs it. Clone only when separate ownership is required.

Comparisons

TypeOwns text?Can the text change?Lifetime annotation needed in a struct?Typical use
StringYesYesNoStruct owns user input or parsed data
&strNoNo through that referenceUsually yesStruct temporarily views existing text
&StringNoNo through that referenceYesRarely needed; prefer &str for read-only text
Cow<'a, str>SometimesCan become ownedYesAvoid an allocation unless modification is necessary

For a first version of a data model, String is often the simplest choice when ownership is required.

String and &str are related but are not interchangeable:

let owned: String = String::from("hello");
let borrowed: &str = &owned;

Rust can borrow a String as &str, but it cannot turn an arbitrary &str into a String without creating an owned copy using .to_owned() or String::from(...).

Take Quiz

Cheat Sheet

// Define owned text fields
struct Person {
    name: String,
    city: String,
}

// Create an instance from literals
let person = Person {
    name: String::from("Noah"),
    city: "Lisbon".to_string(),
};

// Create a String from a &str
let text = "hello".to_owned();

// Move an existing String into a field
let name = String::from("Noah");
let person = Person {
    name,
    city: String::from("Lisbon"),
};

// Borrow a field without moving it
println!("{}", person.name);

// Mutate a field
let mut person = person;
person.city.push_str(", Portugal");

// Constructor that accepts &str or String
fn new_name(name: impl Into<String>) -> String {
    name.into()
}

Rules:

  • Use String when the struct owns text.
  • Use String::from("text"), "text".to_string(), or "text".to_owned() to convert literals to String.
  • Moving a String into a struct transfers ownership.
  • Mark the struct instance mut before changing one of its fields.
  • Use &str parameters when a function only needs to read text temporarily.

FAQ

Should a Rust struct use String or &str?

Use String when the struct should own and retain the text. Use &str when it only borrows text owned elsewhere and its lifetime is clear.

Why does name: "Ada" not compile for a String field?

"Ada" is a string slice (&str). Convert it with String::from("Ada"), "Ada".to_string(), or "Ada".to_owned().

Does moving a String into a struct copy the text?

No. A move transfers ownership of the String value without copying its text buffer. Cloning is the operation that copies the text.

Can I change a String field after constructing the struct?

Yes, if the struct instance is mutable:

let mut user = User { name: String::from("Ada") };
user.name = String::from("Grace");

Why do structs with &str fields need lifetimes?

The struct must not outlive the text it references. Lifetimes let Rust verify that the referenced text stays valid long enough.

Is .to_string() different from String::from() for string literals?

Both create an owned String for ordinary string literals. String::from("text") clearly signals conversion to String; .to_string() works for many types that implement Display.

Can a struct own some fields and borrow others?

Yes. For example, a struct can contain name: String and source: &'a str. The struct then needs a lifetime parameter for the borrowed field.

Take Quiz

Mini Project

Description

Build a small contact record that owns a person's name, email address, and optional note. This mirrors data gathered from a form, command-line input, or an API request, where the resulting struct must keep its data independently.

Goal

Create, validate, display, and update a contact using owned String fields.

Requirements

  1. Define a Contact struct with owned String fields for name and email.
  2. Add an optional owned note using Option<String>.
  3. Create a constructor that accepts text input and stores owned strings.
  4. Reject an empty name or an email without @.
  5. Add a method that updates the contact note.
  6. Print the final contact details.
Take Quiz

Keep learning

Related questions

View more