We have a string that has two parts separated by a character. e.g. a colon like this: "localhost:5000". How can we assign the two parts to two separate variables?
In this example you'll see 4 solutions. Each one has some advantages and some disadvantages.
The shortest solution would be this one:
if let Some((hostname, port)) = text.split_once(':') {
println!("hostname: {hostname}");
println!("port: {port}");
}
However if we cannot be sure that the original string will be of the correct format, then this might give us an invalid result.
Especially if the input string has more than one :
characters then everything that is after the first :
will end up in the variable called `port.
So if the input is localhost:5000:garbage
then the port
will contain 5000:garbage
.
Proper solutions
The solutions somehow need to handle the cases when the original string has more than one separators (:
) and when it has none.
In the first solution, called with_split
, we split the string into parts at the delimiter (:
) using split
, collect
with turbofish. We check if we got exactly two values,
In the second solution, called short_with_split_once_and_some
, we use the split_once
method. This means that if we have more than one :
then the second variable will contain the rest of the string.
In this solution we did not deal with the problem.
The 3rd solution, called with_split_once_and_some
, is the same as the previous, but now we check if we could split the string properly and report if we could not.
The 4th solution, called with_split_once_and_unwrap
, will panic!
if there are more than one colons :
.
examples/split-string-into-two-variables/src/main.rs
fn main() {
for address in [
"localhost:5000",
"localhost:",
"localhost:5000:garbage",
"localhost",
] {
short_with_split_once_and_some(address);
with_split(address);
with_split_once_and_some(address);
with_split_once_and_unwrap(address);
}
}
// The long winded version.
// It will call the optional else part if the number of `:` is not exactly 1.
fn with_split(text: &str) {
println!("{text}");
let parts = text.split(':').collect::<Vec<&str>>();
if parts.len() == 2 {
let (hostname, port) = (parts[0], parts[1]);
println!("hostname: {hostname}");
println!("port: {port}");
} else {
eprintln!("Could not split properly");
}
println!();
}
fn short_with_split_once_and_some(text: &str) {
println!("{text}");
if let Some((hostname, port)) = text.split_once(':') {
println!("hostname: {hostname}");
println!("port: {port}");
}
println!();
}
// A shorter version.
// It will call the optional else part if the number of `:` is not exactly 1.
fn with_split_once_and_some(text: &str) {
println!("{text}");
if let Some((hostname, port)) = text.split_once(':') {
if port.contains(':') {
eprintln!("Too many separators (:)");
} else {
println!("hostname: {hostname}");
println!("port: {port}");
}
} else {
eprintln!("Could not split properly");
}
println!();
}
// The overly confident version
// For "localhost:5000:garbage" this will return "hostname" and "5000:garbage"
// For "localhost" this will panic!
fn with_split_once_and_unwrap(text: &str) {
let (hostname, port) = text.split_once(':').unwrap();
if port.contains(':') {
eprintln!("Too many separators (:)");
} else {
println!("hostname: {hostname}");
println!("port: {port}");
}
println!();
}
Conclusion
If you we confident that the input has the correct format then we can be lazy and write shorter code, but if we want to be robust then we might need to write more code.