Quiz 2 - String Transformer

Overview

This quiz tests your understanding of:

  • Strings — String manipulation methods
  • Vecs — Vectors of tuples
  • Modulesmod, use, and super
  • Enums — Enums with data variants
  • Move Semantics — Ownership in function calls

The Problem

Build a transformer function that:

  1. Takes a vector of (String, Command) tuples
  2. Applies the command to each string
  3. Returns a vector of transformed strings

Commands:

  • Uppercase — Convert to uppercase
  • Trim — Remove leading/trailing whitespace
  • Append(n) — Append “bar” n times

The Full Solution

enum Command {
    Uppercase,
    Trim,
    Append(usize),  // Enum variant with data
}
 
mod my_module {
    use super::Command;  // Import Command from parent module
 
    pub fn transformer(input: Vec<(String, Command)>) -> Vec<String> {
        let mut output = Vec::new();
 
        for (string, command) in input {
            let new_string = match command {
                Command::Uppercase => string.to_uppercase(),
                Command::Trim => string.trim().to_string(),
                Command::Append(n) => string + &"bar".repeat(n),
            };
            output.push(new_string);
        }
 
        output
    }
}
 
#[cfg(test)]
mod tests {
    use super::{my_module::transformer, Command};
    // ...
}

Step-by-Step Code Walkthrough

Step 1: Define the Command Enum

enum Command {
    Uppercase,           // Unit variant (no data)
    Trim,                // Unit variant (no data)
    Append(usize),       // Tuple variant with data (how many times to append)
}

This enum represents the three possible transformations. Append(usize) holds a number indicating how many times to append “bar”.


Step 2: Create a Module and Import

mod my_module {
    use super::Command;  // super = parent module (where Command is defined)

Why super::Command?

  • Command is defined in the parent module (crate root)
  • super means “go up one level”
  • Without this import, Command wouldn’t be visible inside my_module

Step 3: Define the Function Signature

pub fn transformer(input: Vec<(String, Command)>) -> Vec<String>
PartMeaning
pubPublic — accessible from outside the module
input: Vec<(String, Command)>Vector of tuples, each with a String and Command
-> Vec<String>Returns a vector of transformed strings

Step 4: Iterate and Match

for (string, command) in input {
    let new_string = match command {
        Command::Uppercase => string.to_uppercase(),
        Command::Trim => string.trim().to_string(),
        Command::Append(n) => string + &"bar".repeat(n),
    };
    output.push(new_string);
}

Destructuring the tuple: (string, command) extracts both elements.


Step-by-Step Execution

Input:

let input = vec![
    ("hello".to_string(), Command::Uppercase),
    (" all roads lead to rome! ".to_string(), Command::Trim),
    ("foo".to_string(), Command::Append(1)),
    ("bar".to_string(), Command::Append(5)),
];

Iteration 1: ("hello", Command::Uppercase)

string = "hello"
command = Command::Uppercase
 
match command {
    Command::Uppercase => string.to_uppercase(),
    //                    ^^^^^^^^^^^^^^^^^^^^^^
    //                    "hello".to_uppercase() -> "HELLO"
}
 
output = ["HELLO"]

Iteration 2: (" all roads lead to rome! ", Command::Trim)

string = " all roads lead to rome! "
command = Command::Trim
 
match command {
    Command::Trim => string.trim().to_string(),
    //               ^^^^^^^^^^^^^^^^^^^^^^^^^
    //               " all roads lead to rome! ".trim() -> "all roads lead to rome!"
    //               (removes leading/trailing spaces)
    //               .to_string() converts &str back to String
}
 
output = ["HELLO", "all roads lead to rome!"]

Iteration 3: ("foo", Command::Append(1))

string = "foo"
command = Command::Append(1)
 
match command {
    Command::Append(n) => string + &"bar".repeat(n),
    //             ^           ^    ^^^^^^^^^^^^^^^
    //             |           |    "bar".repeat(1) -> "bar"
    //             |           |    & borrows the repeated string
    //             |           + concatenates (takes ownership of left side)
    //             n = 1 (destructured from Append(1))
}
 
// "foo" + &"bar" = "foobar"
 
output = ["HELLO", "all roads lead to rome!", "foobar"]

Iteration 4: ("bar", Command::Append(5))

string = "bar"
command = Command::Append(5)
 
match command {
    Command::Append(n) => string + &"bar".repeat(n),
    //                    "bar" + &"barbarbarbarbar"
    //                            (5 times "bar")
}
 
// "bar" + "barbarbarbarbar" = "barbarbarbarbarbar"
 
output = ["HELLO", "all roads lead to rome!", "foobar", "barbarbarbarbarbar"]

Key Concepts Explained

1. String Concatenation with +

string + &"bar".repeat(n)
  • + operator takes ownership of left side (string)
  • Right side must be &str (borrowed)
  • &"bar".repeat(n)repeat returns String, & borrows it as &str

2. Why .to_string() after .trim()?

string.trim().to_string()
  • .trim() returns &str (a slice of the original)
  • We need to return String, so .to_string() converts it

3. Module Imports with super

mod tests {
    use super::{my_module::transformer, Command};
    //   ^^^^^
    //   Goes up to parent module
    //
    //   my_module::transformer — reaches into the sibling module
    //   Command — defined in parent
}

Module hierarchy:

crate root
├── Command (enum)
├── my_module
│   └── transformer (function)
└── tests
    └── uses super:: to access siblings and parent

Alternative: Iterator Version

pub fn transformer_iter(input: Vec<(String, Command)>) -> Vec<String> {
    input
        .into_iter()                    // Consume the vector, iterate over owned values
        .map(|(string, command)| {      // Transform each tuple
            match command {
                Command::Uppercase => string.to_uppercase(),
                Command::Trim => string.trim().to_string(),
                Command::Append(n) => string + &"bar".repeat(n),
            }
        })
        .collect()                      // Collect into Vec<String>
}

Why into_iter() instead of iter()?

  • into_iter() takes ownership — we get String, not &String
  • We need ownership because + consumes the left-hand String

Concepts Demonstrated

ConceptHow It’s Used
Enums with dataCommand::Append(usize)
Pattern matchingmatch command { ... }
Destructuringfor (string, command) in input
Modulesmod my_module { }
Importsuse super::Command
String methods.to_uppercase(), .trim(), .repeat()
Ownershipstring + &... takes ownership of string
Visibilitypub fn makes function public