image courtesy – www.rust-lang.org
Rust
String concatenation
As a programming language, we introduced ourselves to it some time back. We got hang of the trait is the
most loved and advertised feature of the language. While building a prototype of an engagement we
stumbled upon the purpose of rust and probably the innate reason why developers cannot stop expressing
their love for this language. We are talking about the string concatenation in rust. In any modern
programming language, this will not come across as an interesting topic to talk. Come on what could be
ever interesting about –
let greeting = "hello";
let recipient = "reader";
console.log(greeting + recipient);
For us, the discovery of string concatenation in rust was like finding the purpose of life for Siddartha who
then transformed into Lord Buddha.
Owned and borrowed types
There are multiple ways with their own merits and demerits when it comes to concatenating strings. For us
to realise the reason for those merits and demerits we must have a good grasp on owned and borrowed
types within rust. The humble names reflect their purposes. Owned type owns the data it contains. With
ownership comes the responsibility of managing memory in the heap. String is an owned type.
The borrowed type comes with the right to access data but is free from the responsibility of managing
memory for the data. These types live only as long as the data they have access to lives and comes with
an expiration date; to say if it sounds interesting to say. &str which is also a string type is an example of
the borrowed type.
The symbol & might have stricken a chord that often relates to unpleasant pointers from the world of C and
C++ programming languages. Though the symbol actually refers to the address-of-operator but is related to
the concept of pointers.
In this world of rust, the & does indicate a borrowed type and the pointer is one among them. However,
there is a spectrum that must be tabled –
1. Reference pointers, correspond to what we know from the world of C and C++. However, they use
the symbol related to the address-of-operator from the C++ diction.
2. Raw pointers, correspond to what is known as unsafe pointers and are not borrowed types. They
use the same symbol as in C++ language for pointers.
3. Smart pointers are what they are called. But are not borrowed types. Instead, they are owned types.
They do not use any & or * symbols but are generic mostly.
Let us mix two strings
A couple of the useful and powerful ways of mixing two strings involve calling a macro.
concat! macro
fn main() {
let mut _greeting = concat!("Greeting", "human");
println!(_greeting);
}
format! Macro
fn main() {
let human = "earthling";
println!(format!("Greetings {}", human));
}
Macros are what they are i.e., accelerators to write code for programmers. They are the first line of
automation in generating code. Thus, the underlying messages for string concatenation are not very
apparent in the usage of a macro. They however serve you the purpose for which you will include them in
your code, i.e., they concatenate strings.
Oh, before we proceed further, we want to call out a subtle underlying reality of the format! macro. It does
not modify or lessen the reference count for the underlying string variable. So, if your need is to
concatenate strings and keep using the variable which was concatenated, then format! is the macro you
must use.
There are two more ways of concatenating strings. One uses the push_str method on the owned type of
string. Another uses the Add trait implementation on String (owned type).
push_str method
fn main() {
let mut captain: String = "Picard".to_owned();
let operative: String = " commands onboard".to_owned();
let ship: &string = " USS Enterprise";
captain.push_str(&operative);
println!("{}", captain.push_str(ship));
}
This will print – Picard commands onboard USS Enterprise. The push_str method is defined on the owned
type of String which takes in a &str borrowed type. In the code above we have also demonstrated the type
conversion of String to &str. You will use this if you approach if you want to grow a String variable to
contain the newly constructed string. In this example, it is the variable captain. Thus, using the existing
buffer and expanding when needed based on the number of push_str calls.
+ operator
fn main() {
let captain: String = "Picard".to_owned();
let operative: String = " commands onboard".to_owned();
let ship: &string = " USS Enterprise";
let statement = captain + operative + ship;
println!("{}", statement);
}
The output from this code is the same as from the push_str. Notice we are not making the String
mutable. i.e., captain and operative are owned but non-mutable. The flip side of this approach of
concatenation is you will not be able to refer to captain and operative once concatenated this way. Thus,
this style will help you if you are interested only in the final concatenated string and do not wish to reuse the
base variable itself.
Purpose of life
Though we titled the section as the purpose of life, it is more for the life of rust as a programming language
and not you. One cannot deny the foundational nature of working with string. It is the atomic unit of
meaningful computation from a human's perspective. The different ways of operating on such a
foundational type reflect the power which this language gives to developers who wield these powers. All of
this without losing out on being safe from developers who could make rookie mistakes. Isn't that the reason
a new language be built?