overexact.com
Rustforprofessionals
Introduction
Naming
Syntax
Variables
Types
Mutability
Destructuring
Functions
Innerfunctions
Extensionmethods
Closures(lambdas)
Expressions
Structs(classes)
Traits(interfaces)
Defaultmethods
Associatedfunctions
Enums
Associatedvalues
Concepts
Ownership
Strings
Nullvalues
Errorhandling
Input
Attributes(annotations)
Miscellaneous
Packagemanagement
Projectsetup
Usefullinks
Introduction ↑
ThisisashortintroductiontoRust,intendedfordevelopersthatalreadyknowanother
language.Intheexamples,RustiscomparedwithTypeScript,JavaScriptorJava,sometimes
withC++orKotlin.
ForadeepdiveintothesyntaxandRust’sconcepts,havealookatTheRustProgramming
Language,butforaquickoverview,readon.
Naming
Regardingnames,Rustpreferssnakecaseforvariablesandfunctions,soamethodwould
becalled read_str insteadof readStr.Forstructs,traitsandenums,camelcase(orPascal
case)isused,forexample HttpClient.
Syntax
Rust’ssyntaxisamixofexistinglanguages(curlybraces,functionsandreferenceslikeinC,
typeafteridentifierlikeinGoorKotlin,genericsandtypeparameterslikeinC++orJava)
withsomeRust-specificelements(lifetimenames,patterns,macros,attributes).Foraquick
overviewofthesyntax,seetheRustLanguageCheatSheetoranoverviewofRust’s
keywords.
Variables
RustvariabledeclarationsareverysimilartoTypeScriptorKotlin,butlookabitdifferentfrom
JavaorC.
const s: string = "";
let n: number = 0.9;
let i = 123; // Type inferred
let s: &str = "";
let mut n: f64 = 0.9;
let mut i = 123; // Type inferred
Mostofthetime,thetypecanbeommittedinRustandthecompilerwillinferthecorrecttype
Types
InRust,therearemorespecificprimitivedatatypes.
Thevoidtypeiscalledunitandisindicatedby (),seefunctionsforanexample.
int i = 123;
long l = 456L;
float f = 0.5f;
double d = 0.5;
TypeScript
Rust
Java
↑
String string = "Hello";
int[] arr = {1, 2, 3};
List<Integer> list = Arrays.asList(1, 2, 3);
let i: i32 = 123;
let l: i64 = 456;
let f: f32 = 0.5;
let d: f64 = 0.5f64;
let string: &str = "Hello";
let arr: [i32; 3] = [1, 2, 3];
let list: Vec<i32> = vec![1, 2, 3];
InRust,numericliteralscanoptionallyhaveatypesuffix,forexample1000u32 or0.5f64.
Mutability
Variablesneedtobeexplicitlydeclaredmutable(let versus let mut),likeinJavaScript
(const and let)orKotlin(val and var).
ThemutabilitymodelinRustisnotlikeJavaScript,butabitmorelikeconstinC++,asRust
willnotletyoucallmodifyingmethodsonavariablewhichisnotdeclaredasmutable.
let arr1: string[] = [];
arr1.push("123"); // OK
arr1 = ["a", "b"]; // OK
const arr2: string[] = [];
arr2.push("123"); // OK, even though arr2 is const
arr2 = []; // error, arr2 is const
let mut arr1 = vec![];
arr1.push("123"); // OK
arr1 = vec!["a", "b"]; // OK
let arr2 = vec![];
arr2.push("123"); // error, arr2 is not mutable
arr2 = vec![]; // error, arr2 is not mutable
InTypeScript,declaringavariableasconst onlypreventsreassignment,notmodification.InRust,only
variablesdeclaredasmut canbemodified
Destructuring
Rust
TypeScript
Rust
↑
Rustsupportsdestructuring,likeJavaScriptorKotlin.
function distance(a, b) {
const { x: x1, y: y1 } = a;
const { x: x2, y: y2 } = b;
return Math.sqrt(
Math.pow(x2 - x1, 2) +
Math.pow(y2 - y1, 2)
);
}
fn distance(a: &Point, b: &Point) -> f32 {
let Point { x: x1, y: y1 } = a;
let Point { x: x2, y: y2 } = b;
((x2 - x1).powf(2.0) + (y2 - y1).powf(2.0)).sqrt()
}
struct Point {
x: f32,
y: f32,
}
MoreexamplescanbefoundinthissectionfromtheRustforC++programmersguide
Functions
FunctionsarebasicallythesameasinC,Java,GoorTypeScript:Theyhaveaname,zeroor
moreparametersandareturntype.
void log(char* message) {
printf("INFO %sn", message);
}
fn log(message: &str) -> () {
println!("INFO {}", message);
}
Theunittype() (voidinsomelanguages)isthedefaultreturntypewhennotypeisgivenforafunction.It
couldbeomittedinthisexample,likefn log(message: &str) { ... }
InRust,functionsareexpressions,whichmeansthelaststatementisalsothereturnvalue
(likeinRuby).Thisisabitlikeimplicitreturn,butnotexactly.Theofficialstyleistoonlyuse
return forearlyreturns.
public static int add(int a, int b) {
return a + b;
}
JavaScript
Rust
C
Rust
Java
↑
fn add(a: i32, b: i32) -> i32 {
a + b
}
NotethatthereisnosemicolonintheRustfunction,otherwiseitwouldreturnvoid
Rustcurrentlyhasnonamedargumentsordefaultarguments(likePythonandTypeScript)
anddoesnotallowmethodoverloading(likeC++,JavaorTypeScript).
Innerfunctions
Rustalsosupportsinnerfunctions.
const RE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
function isValidRange(start, end) {
function isValid(date) {
return date && date.match(RE);
}
return isValid(start) && isValid(end);
}
use regex::Regex;
const RE: &str = r"^[0-9]{4}-[0-9]{2}-[0-9]{2}$";
fn is_valid_range(start: &str, end: &str) -> bool {
fn is_valid(date: &str) -> bool {
!date.is_empty()
&& Regex::new(RE)
.unwrap()
.is_match(date)
}
is_valid(start) && is_valid(end)
}
NotethatRegex isanexternalcrateandnotpartofRust’sstandardlibrary
Extensionmethods
Rust(likeKotlin)alsosupportsextensionmethods,sothepreviousexamplecouldbe
rewrittenusingextensions.
typealias Range = Pair<String, String>
fun Range.isValid(): Boolean {
val (start, end) = this
Rust
JavaScript
Rust
Kotlin
↑
return start.isNotEmpty() && end.isNotEmpty()
}
object Main {
@JvmStatic
fun main(args: Array<String>) {
val range = Range("2020-01-01", "2020-12-31")
if (range.isValid()) {
println("Range is valid!")
}
}
}
type Range<'r> = (&'r str, &'r str);
trait IsValid {
fn is_valid(&self) -> bool;
}
impl<'r> IsValid for Range<'r> {
fn is_valid(&self) -> bool {
let (start, end) = &self;
!start.is_empty() && !end.is_empty()
}
}
fn main() {
let range = ("2020-01-01", "2020-12-31");
if range.is_valid() {
println!("Range is valid!");
}
}
InRust,extensionmethodsareaddedbyimplementingatrait.Whenthereisonlyonemethod,it’scommon
tonamethetraitlikethemethod(IsValid).The'r denotesalifetime,formoreinformation,seethis
sectionfromtheRustbook
Closures(lambdas)
Rustsupportsclosures(alsocalledLambdas,arrowfunctionsoranonymousfunctionsin
otherlanguages).
Whenaccessingvariablesfromoutsidetheclosure,RustismorestrictthanJavaScriptor
Java,seecapturingformoredetails.
function findEmails(list) {
return list.filter(
s => s && s.includes("@")
Rust
JavaScript
↑
);
}
fn find_emails(list: Vec<String>) -> Vec<String> {
list.into_iter()
.filter(|s| s.contains("@"))
.collect()
}
Formorefilterexamples,seethissectionfromthedocumentation
Expressions
InRust,almosteverythingisanexpression,likeinKotlinanddifferentfromJavaScriptor
Java.Youcandirectlyassigntheresultofan if statementtoavariable,forexample.
function getLogLevel() {
let level = process.env.TRACE
? "trace"
: process.env.DEBUG
? "debug"
: "info";
level =
level === "trace"
? 0
: level === "debug"
? 1
: 2;
console.log("using log level", level);
return level;
}
fn get_log_level() -> u32 {
let level = if std::env::var("TRACE").is_ok() {
"trace"
} else if std::env::var("DEBUG").is_ok() {
"debug"
} else {
"info"
};
let level = match level {
"trace" => 0,
"debug" => 1,
_ => 2,
Rust
JavaScript
Rust
↑
};
println!("using log level {}", level);
level
}
TheRustcodeusesmatch,whichislikeswitch inJavaorJavaScriptexceptthatit’sanexpressionand
providesmoreflexibility.Unlessmanyotherlanguages,Rustallowsvariableshadowingforlocalvariables
(level inthisexample)
Structs(classes)
RustdoesnothavefullsupportforclasseslikeJavaorTypeScript,butinsteadoffersstructs
(similartostructsinC).Thesearelikedatacontainerswithmethods,buttheydon’tsupport
alloftheobjectorientedconcepts,likeinheritance.
public class HttpClient {
private final ClientImpl clientImpl;
public HttpClient() {
clientImpl = new ClientImpl();
}
public String get(String url) {
return clientImpl.newRequest()
.get(url).asString();
}
}
public static void main(String[] args) {
HttpClient httpClient = new HttpClient();
System.out.println(httpClient
.get("https://example.com/"));
}
pub struct HttpClient {
client_impl: ClientImpl,
}
impl HttpClient {
pub fn new() -> HttpClient {
HttpClient {
client_impl: ClientImpl {},
}
}
pub fn get(&self, url: &str) -> String {
self.client_impl.new_request()
Java
Rust
↑
.get(url)
.as_string()
}
}
fn main() {
let http_client = HttpClient::new();
println!("{}",
http_client.get("https://example.com/"));
}
InJava,mutabilityisgivenonafieldlevel,hereclientImpl isimmutable.InRust,themutabilitymodifieris
setontheinstancevariableandnotperfield,soyoucannothavemutableandimmutablefieldsinthe
samestruct(seetheInteriorMutabilityPattern)
Methodsdonothaveimplicitaccesstothis,asinJavaorJavaScript,soyouneedtopassan
explicitargumentnamed self,likeinPython.Methodswithoutthisparameterarecalled
associatedfunctions(theyarecalledstaticmethodsinsomelanguages).
InRust,thereisnoconstructor,structsarecreatedsimilarlytoobjectsinJavaScript.For
caseswhereconstructorlogicisrequired,thereisaconventiontocreateanassociated
function(staticmethod)named new,whichwillreturntheconstructedobjectinstance.
Traits(interfaces)
ThemostsimilarthingtointerfacesinRustaretraits.
interface Named {
fun name(): String
}
data class User(
val id: Int,
val name: String
) : Named {
override fun name(): String {
return this.name
}
}
pub trait Named {
fn name(&self) -> &String;
}
pub struct User {
pub id: i32,
pub name: String,
}
Kotlin
Rust
↑
impl Named for User {
fn name(&self) -> &String {
return &self.name;
}
}
Rustismostlyastaticlanguage,sosomethingsthatotherlanguagewilldoduringruntime,
Rustwilldoduringcompiletime,whenpossible.Interfacesareusuallyusedfordynamic
dispatchandifyouwanttousetraitsinasimilarway,seethischapteraboutstaticand
dynamicdispatchandthisblogpost.
Defaultmethods
Traitsalsosupportdefaultmethods,likeinterfacesinJavaorKotlin.
Associatedfunctions
InRust,traitscanalsohaveassociatedfunctions,forexample from_str in
std::str::FromStr (sectionstringparsingintheRustCookbook)
interface FromList<T> {
fun fromList(list: List<Int>): T?
}
data class MyPoint(val x: Int, val y: Int) {
companion object : FromList<MyPoint> {
override fun fromList(list: List<Int>):
MyPoint? {
return when (list.size) {
2 -> MyPoint(list[0], list[1])
else -> null
}
}
}
}
object Main {
@JvmStatic
fun main(args: Array<String>) {
val point = MyPoint.fromList(listOf(100, 200))
println(point)
}
}
trait FromList<T> {
fn from_list(list: &Vec<i32>) -> Option<T>;
}
struct MyPoint {
Kotlin
Rust
↑
x: i32,
y: i32,
}
impl FromList<Self> for MyPoint {
fn from_list(list: &Vec<i32>) -> Option<Self> {
match list.len() {
2 => Some(MyPoint {
x: list[0],
y: list[1],
}),
_ => None,
}
}
}
fn main() {
let point =
MyPoint::from_list(&vec![100, 200]).unwrap();
println!("({}, {})", point.x, point.y);
}
FormoreinformationabouttheOption type,seetheNullvaluessectionbelow.ThekeywordSelf (upper
case)canbeusedtoreferencethecurrenttype
Enums
Rustsupportsenums,likeJavaorKotlin,butwithmoreflexibility.TheRustenumsoffermore
thaninC++orTypeScript,astheyarenotmerelyalistofconstants,butmorelikeunions.
enum UserRole {
RO("read-only"), USER("user"),
ADMIN("administrator");
private final String name;
UserRole(String name) {
this.name = name;
}
String getName() {
return name;
}
boolean isAccessAllowed(String httpMethod) {
switch (httpMethod) {
case "HEAD":
case "GET":
return true;
Java
↑
case "POST":
case "PUT":
return this == USER || this == ADMIN;
case "DELETE":
return this == ADMIN;
default:
return false;
}
}
}
class Main {
public static void main(String[] args) {
UserRole role = UserRole.RO;
if (role.isAccessAllowed("POST")) {
System.out.println("OK: "
+ role.getName());
} else {
System.out.println("Access denied: "
+ role.getName());
}
}
}
#[derive(PartialEq)]
enum UserRole {
RO,
USER,
ADMIN,
}
impl UserRole {
fn name(&self) -> &str {
match *self {
UserRole::RO => "read-only",
UserRole::USER => "user",
UserRole::ADMIN => "administrator",
}
}
fn is_access_allowed(
&self,
http_method: &str,
) -> bool {
match http_method {
"HEAD" | "GET" => true,
"POST" | "PUT" => {
*self == UserRole::USER
Rust
↑
|| *self == UserRole::ADMIN
}
"DELETE" => *self == UserRole::ADMIN,
_ => false,
}
}
}
fn main() {
let role = UserRole::RO;
if role.is_access_allowed("POST") {
println!("OK: {}", role.name());
} else {
println!("Access denied: {}", role.name());
}
}
Rustdoesnotsupportconstantsinenums,soweneedtouseamatchinthename method.Theenum
matchesarecheckedatcompiletime,sowhenallenumvariantsareused,thereisnoneedtohavea
defaultbranch.
Formoreinformationaboutthe#[derive(PartialEq)] line,seethesectionaboutAttributes
Associatedvalues
Rustenumsalsosupportassociatedvalues,whichmeanstheyarenotconstant,butinstead
allowthecreationofenumvariantinstanceswithspecificvalues.
sealed class GitCommand {
abstract fun execute()
}
object Status : GitCommand() {
override fun execute() =
executeCommand(listOf("status"))
}
class Checkout(
private val branch: String
) : GitCommand() {
override fun execute() =
executeCommand(listOf("checkout", branch))
}
class Add(
private val files: List<String>
) : GitCommand() {
override fun execute() =
executeCommand(listOf("add") + files)
}
Kotlin
↑
class Log(
private val decorate: Boolean,
private val patch: Boolean
) : GitCommand() {
override fun execute() {
val args = mutableListOf("log")
if (decorate) {
args.add("--decorate")
}
if (patch) {
args.add("--patch")
}
executeCommand(args)
}
}
fun executeCommand(args: List<String>) {
val redirect = ProcessBuilder.Redirect.INHERIT
ProcessBuilder(listOf("git") + args)
.redirectInput(redirect)
.redirectOutput(redirect)
.redirectError(redirect)
.start()
.waitFor()
}
object Main {
@JvmStatic
fun main(args: Array<String>) {
val command =
Log(decorate = false, patch = true)
command.execute()
}
}
use std::process::Command;
pub enum GitCommand<'g> {
STATUS,
CHECKOUT(&'g str),
ADD(Vec<&'g str>),
LOG { decorate: bool, patch: bool },
}
impl<'g> GitCommand<'g> {
fn execute(self) {
let args: Vec<&str> = match self {
GitCommand::STATUS => vec!["status"],
GitCommand::CHECKOUT(branch) => {
vec!["checkout", branch]
Rust
↑
}
GitCommand::ADD(files) => {
[vec!["add"], files].concat()
},
GitCommand::LOG {
decorate,
patch,
} => {
let mut args = vec!["log"];
if decorate {
args.push("--decorate")
}
if patch {
args.push("--patch")
}
args
}
};
execute_command("git", &args);
}
}
fn execute_command(command: &str, args: &[&str]) {
Command::new(command)
.args(args)
.spawn()
.expect("spawn failed!")
.wait()
.expect("command failed!");
}
fn main() {
let command = GitCommand::LOG {
decorate: false,
patch: true,
};
command.execute();
}
SealedClassesinKotlinaresimilartoRust’senums,astheyallowthemodellingofaclosedtypehierarchy
Seethissectionaboutenumsformoreexamples.
Concepts
Ownership
OnethingthatisveryspecialaboutRustisthewayithandlesmemoryallocations:Itdoesn’t
haveagarbagecollector(likeJavaScript,JavaorGo),butthedeveloperdoesnotneedto ↑
freememoryexplicitly,either(likeinC).Instead,Rustautomaticallyfreesmemorywhenitis
nolongerinuse.Forthismechanismtowork,thedeveloperneedstoexplicitlythinkabout
ownershipofthevaluestheprogramisusing.
class User {
private String name;
public User(String name) {
this.name = name;
}
}
public static void main(String[] args) {
String name = "User";
User user1 = new User(name);
User user2 = new User(name);
}
struct User {
name: String,
}
fn main() {
let name = String::from("User");
let user1 = User { name };
let user2 = User { name }; // compile error
}
InJava,thegarbagecollectorwillregularlychecktheobjectreferencesandwhennothingreferencesthe
Userinstancesanymore,theywillbedeleted.Oncebothinstanceshavebeendetectedasunused,the
"User" stringcanalsobedeleted.
InRust,valuescanonlybeownedbyoneobjectatatime:theassignmenttouser1 isOK,becausethe
valuewillbemovedfromthename variabletouser1.Thecreationofuser2,however,willgiveacompile
error,becausethestring"User" isnowownedbyuser1,notbyname.
InC,thedeveloperhastomakesurethatallocatedmemoryisfreedwhenitisnolonger
needed.Thiscanleadtobugs(memoryleaks)andwasoneofthemotivationforRust’s
differentapproachtomanagingmemory.WhileRustoffersprotectionsagainstit,it’snot
impossibletohavememoryleaks.
#include <string>
std::string* get_string() {
std::string* string = new std::string("hello");
delete string;
Java
Rust
C++
↑
return string;
}
fn get_string() -> String {
let string = String::from("hello");
drop(string);
return string; // compile error!
}
Inthisexample,theC++codehasauseafterfreeerror.Notethatthisisonlyanexample,dropisrarely
usedinRustcode(valueswillbedroppedautomaticallywhentheygooutofscope)
Rust’sownershipmodelalsohelpswhendealingwithmulti-threadedcode.Thecompiler
keepstrackofthevaluestheprogramisusingandmakessurethatthesamevalueisnot
accessedfrommultiplethreadswithoutproperlockinglogicaroundit.
#include <vector>
#include <thread>
int main() {
std::vector<std::string> list;
auto f = [&list]() {
for (int i = 0; i < 10000; i++) {
list.push_back("item 123");
}
};
std::thread t1(f);
std::thread t2(f);
t1.join();
t2.join();
}
use std::thread;
fn main() {
let mut list = vec![];
let f = move || {
for _ in 0..10000 {
list.push("item 123");
}
};
Rust
C++
Rust
↑
let t1 = thread::spawn(f);
let t2 = thread::spawn(f); // compile error!
t1.join().unwrap();
t2.join().unwrap();
}
Thestd::vector classisnotthread-safeandtheC++programwillcompilewithouterrors,butwhen
running,itwillprobablycrashwithanerrorlikepointer being freed was not allocated orsimilar.In
Rust,theclosuref takesownershipoflist (indicatedbythemovekeyword),that’swhythecompilergives
anerrorwhenf isusedmorethanonce.
Strings
Rusthasmultiplestringtypes,themostimportantonesarestr(usuallyintheformof &str)
andString.
The &str typeonlyreferencesborrowedcontent,sothisisthetypethatisusedforstatic
stringsandwhenreferencingstringslices.Likeotherimmutablereferences, &str values
cannotbemodified.Use String ifyouneedamodifiablestring.
const FILE_DATE = "2020-01-01";
function printCopyright() {
const year = FILE_DATE.substr(0, 4);
const copyright = `(C) ${year}`;
console.log(copyright);
}
const FILE_DATE: &str = "2020-01-01";
fn print_copyright() {
let year: &str = &FILE_DATE[..4];
let copyright: String = format!("(C) {}", year);
println!("{}", copyright);
}
Seethissectionaboutstringsformoreexamples
The String typeisusedfordynamicallycreatedstrings,whichcanbemodifiedandwhere
thelengthisnotfixedatcompiletime.
Usually, &str willbeusedforfunctionparametersand String willbeusedasreturnvalue.
public static String repeat(String s, int count) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < count; i++) {
result.append(s);
}
JavaScript
Rust
Java
↑
return result.toString();
}
fn repeat(s: &str, count: u32) -> String {
let mut result = String::new();
for _ in 0..count {
result += s;
}
result
}
Thisisjustanexample,RustalreadyhasString.repeat
Forstructfields,usually String shouldbeused,asthevaluewillprobablybeownedbythat
struct,especiallywhendealingwithlongerlivingobjects.
Forstringconstants, &'static str canbeusedforfieldsinstead.
data class User(
private val source: String,
private val name: String,
private val address: String
) {
companion object {
fun fromString(string: String): User {
val lines = string.lines()
return User("kotlin-v1.0", lines[0],
lines[1])
}
}
}
pub struct User {
source: &'static str,
name: String,
address: String,
}
impl User {
pub fn new(s: &str) -> User {
let mut lines = s.lines();
User {
source: "rust-v1.0",
name: lines.next().unwrap().to_owned(),
address: lines.next().unwrap().to_owned(),
}
}
}
Rust
Kotlin
Rust
↑
Notethatdefaultfieldaccessisprivate inRust
Toconvertavariable s1 oftype String to &str,use &s1.
Toconvertavariable s2 oftype &str to String,use s2.to_owned() (thisallocatesnew
memoryandcreatesacopyofthestring).Sometimesthisconversionisalsonecessaryfor
literals,like "string literal".to_owned().
Nullvalues
Rustdoesnothaveaspecial null value(alsocalled None or nil insomelanguages).
Instead,thereistheOptionenum,whichisverysimilartotheOptionaltypeinJava.
public static Integer getYear(String date) {
if (date.length() >= 4) {
String s = date.substring(0, 4);
try {
return Integer.valueOf(s);
} catch (NumberFormatException e) {
return null;
}
} else {
return null;
}
}
public static void main(String[] args) {
Integer year = getYear("2020-01-01");
if (year != null) {
System.out.println(year);
}
}
fn get_year(date: &str) -> Option<u32> {
if date.len() >= 4 {
let s = date[..4];
match s.parse() {
Ok(year) => Some(year),
Err(_) => None
}
} else {
None
}
}
fn main() {
if let Some(year) = get_year("2020-01-01") {
println!("{}", year);
Java
Rust
↑
}
}
TheOption typeisjustaregularenumfromRust’sstandardlibrary,withthetwoentriesNone andSome.
WhenreturningOption,empty(null)valuescanbereturnedasNone andnon-emptyvaluesneedtobe
wrapped,likeSome(year)
Tosimplycheckifavalueis null,withoutneedingtogettheactualvalue, is_none() can
beused.
Integer year = getYear("");
if (year == null) {
System.err.println("Invalid date given!");
}
let year: Option<u32> = get_year("");
if year.is_none() {
println!("Invalid date given!");
}
Sometimesit’susefultorunsomecodeonlyifavalueis null,toensureavariablehasa
non-null value.TherearemanywaystodoitinRust,butusing match offersthemost
concisesyntax.
String url = "https://github.com";
String content = cache.get(url);
if (content == null) {
content = loadUrl(url);
}
let url = "https://github.com";
let content = match cache.get(url) {
Some(content) => content,
None => load_url(url),
};
NotethatintheRustcode,content isimmutable(toachievethesameinJava,wewouldneedtointroduce
anotherlocalvariableoranewmethod)
Errorhandling
RustdoesnotofferexceptionslikeC++,JavaorJavaScript.Instead,errorconditionsare
indicatedviathemethod’sregularreturnvalue,likeinCorGo.
Java
Rust
Java
Rust
↑
package main
import (
"fmt"
"log"
"strconv"
)
func ParsePort(port string) (uint16, error) {
p, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return 0, err
}
if p == 0 {
return 0, fmt.Errorf("invalid: %d", p)
}
return uint16(p), nil
}
func main() {
port, err := ParsePort("123")
if err != nil {
log.Fatalf("failed to parse port: %v", err)
}
log.Printf("port: %d", port)
}
use std::error::Error;
fn parse_port(s: &str) -> Result<u16, Box<Error>> {
let port: u16 = s.parse()?;
if port == 0 {
Err(Box::from(format!("invalid: {}", port)))
} else {
Ok(port)
}
}
fn main() {
match parse_port("123") {
Ok(port) => println!("port: {}", port),
Err(err) => panic!("{}", err),
}
}
InRustit’scommontohavestatementswhichcoverallpossiblecases,eitherusingif/else ormatch,so
youarelesslikelytoencounteranearlyreturn inRustthaninGo
Go
Rust
↑
Inthepreviousexample,weneededtouse Box<Error>,becausethereturnederrortype
cannotbedeterminedduringcompiletime:Itwilleithercontainaninstanceof
std::num::ParseIntError (fromtheparsemethod,whenparsingfails),orastring(when
theportiszero).
The ? intheline let port: u16 = s.parse()? isthe?operator:when parse returnedan
error, parse_port willreturnthaterror,otherwisetheunwrappedresultof parse willbe
assignedto port.
Input
Rustisoftenusedforterminalapplications,soitalsosupportsreadinginput.Therelevant
codeisinthestd::iomodule.
const { stdin } = require("process");
function readStr() {
return new Promise((resolve, reject) => {
let result = "";
stdin.setEncoding("utf8");
stdin.on("readable", () => {
let chunk;
while ((chunk = stdin.read())) {
result += chunk;
}
});
stdin.once("error", err => reject(err));
stdin.once("end", () => resolve(result));
});
}
use std::io::{stdin, Read};
fn read_str() -> Option<String> {
let mut buffer = String::new();
match stdin().read_to_string(&mut buffer) {
Ok(_) => Some(buffer),
Err(err) => {
eprintln!(
"Error while reading input: {}",
err
);
None
}
JavaScript
Rust
↑
};
}
NotethattheJavaScriptcodereturnsaPromise,soit’sasynchronous,whiletheRustexamplecodeis
synchronous
Attributes(annotations)
Rustalsosupportsattributes(alsocalledannotationsinotherlanguages).Theyare
interpretedduringcompiletimeandarecomparabletopreprocessormacrosinCorC++.
#include <cstdint>
[[nodiscard]] int32_t add(int32_t a, int32_t b) {
return a + b;
}
int main() {
add(1, 2); // warning: unused return value
}
#[must_use]
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
add(1, 2); // warning: unused return value
}
Averycommonattributeisderive,whichcanbeusedtoquicklyimplementtraits.Itisoften
usedlike #[derive(Debug, PartialEq)],toimplementtheDebugandPartialEqtraits.
Miscellaneous
Packagemanagement
RustusuallyusestheCargopackagemanager,whichislikenpmoryarnforJavaScriptor
MavenforJava.
Thepackageregistryisavailableatcrates.io.
Projectsetup
IfyouhaveusedESLintbeforeforJavaScriptorTypeScript,rust-clippyisasimilartoolfor
Rust,whichdetectscommonmistakesandbugs.
C++
Rust
↑
Theequivalentforprettierorgofmtisrustfmt,whichautomaticallyformatscodebasedon
theofficialRuststyleguide.
Usefullinks
TheexcellentRustbook:https://doc.rust-lang.org/book/
RustbyExample:https://doc.rust-lang.org/rust-by-example/
TheRustCookbook:https://rust-lang-nursery.github.io/rust-cookbook
RustLanguageCheatSheet:https://cheats.rs/
RustREPL:https://crates.io/crates/runner
CommentsaboutRust:https://brson.github.io/fireflowers/
Thanksforreading!Ifyouhaveanyquestions,remarks,orjustwanttosaythanks,feelfree
tocontactmeonTwitter
Home Twitter GitHub KeyBase
↑

Rust for professionals.pdf

  • 1.
  • 2.
    ThisisashortintroductiontoRust,intendedfordevelopersthatalreadyknowanother language.Intheexamples,RustiscomparedwithTypeScript,JavaScriptorJava,sometimes withC++orKotlin. ForadeepdiveintothesyntaxandRust’sconcepts,havealookatTheRustProgramming Language,butforaquickoverview,readon. Naming Regardingnames,Rustpreferssnakecaseforvariablesandfunctions,soamethodwould becalled read_str insteadofreadStr.Forstructs,traitsandenums,camelcase(orPascal case)isused,forexample HttpClient. Syntax Rust’ssyntaxisamixofexistinglanguages(curlybraces,functionsandreferenceslikeinC, typeafteridentifierlikeinGoorKotlin,genericsandtypeparameterslikeinC++orJava) withsomeRust-specificelements(lifetimenames,patterns,macros,attributes).Foraquick overviewofthesyntax,seetheRustLanguageCheatSheetoranoverviewofRust’s keywords. Variables RustvariabledeclarationsareverysimilartoTypeScriptorKotlin,butlookabitdifferentfrom JavaorC. const s: string = ""; let n: number = 0.9; let i = 123; // Type inferred let s: &str = ""; let mut n: f64 = 0.9; let mut i = 123; // Type inferred Mostofthetime,thetypecanbeommittedinRustandthecompilerwillinferthecorrecttype Types InRust,therearemorespecificprimitivedatatypes. Thevoidtypeiscalledunitandisindicatedby (),seefunctionsforanexample. int i = 123; long l = 456L; float f = 0.5f; double d = 0.5; TypeScript Rust Java ↑
  • 3.
    String string ="Hello"; int[] arr = {1, 2, 3}; List<Integer> list = Arrays.asList(1, 2, 3); let i: i32 = 123; let l: i64 = 456; let f: f32 = 0.5; let d: f64 = 0.5f64; let string: &str = "Hello"; let arr: [i32; 3] = [1, 2, 3]; let list: Vec<i32> = vec![1, 2, 3]; InRust,numericliteralscanoptionallyhaveatypesuffix,forexample1000u32 or0.5f64. Mutability Variablesneedtobeexplicitlydeclaredmutable(let versus let mut),likeinJavaScript (const and let)orKotlin(val and var). ThemutabilitymodelinRustisnotlikeJavaScript,butabitmorelikeconstinC++,asRust willnotletyoucallmodifyingmethodsonavariablewhichisnotdeclaredasmutable. let arr1: string[] = []; arr1.push("123"); // OK arr1 = ["a", "b"]; // OK const arr2: string[] = []; arr2.push("123"); // OK, even though arr2 is const arr2 = []; // error, arr2 is const let mut arr1 = vec![]; arr1.push("123"); // OK arr1 = vec!["a", "b"]; // OK let arr2 = vec![]; arr2.push("123"); // error, arr2 is not mutable arr2 = vec![]; // error, arr2 is not mutable InTypeScript,declaringavariableasconst onlypreventsreassignment,notmodification.InRust,only variablesdeclaredasmut canbemodified Destructuring Rust TypeScript Rust ↑
  • 4.
    Rustsupportsdestructuring,likeJavaScriptorKotlin. function distance(a, b){ const { x: x1, y: y1 } = a; const { x: x2, y: y2 } = b; return Math.sqrt( Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2) ); } fn distance(a: &Point, b: &Point) -> f32 { let Point { x: x1, y: y1 } = a; let Point { x: x2, y: y2 } = b; ((x2 - x1).powf(2.0) + (y2 - y1).powf(2.0)).sqrt() } struct Point { x: f32, y: f32, } MoreexamplescanbefoundinthissectionfromtheRustforC++programmersguide Functions FunctionsarebasicallythesameasinC,Java,GoorTypeScript:Theyhaveaname,zeroor moreparametersandareturntype. void log(char* message) { printf("INFO %sn", message); } fn log(message: &str) -> () { println!("INFO {}", message); } Theunittype() (voidinsomelanguages)isthedefaultreturntypewhennotypeisgivenforafunction.It couldbeomittedinthisexample,likefn log(message: &str) { ... } InRust,functionsareexpressions,whichmeansthelaststatementisalsothereturnvalue (likeinRuby).Thisisabitlikeimplicitreturn,butnotexactly.Theofficialstyleistoonlyuse return forearlyreturns. public static int add(int a, int b) { return a + b; } JavaScript Rust C Rust Java ↑
  • 5.
    fn add(a: i32,b: i32) -> i32 { a + b } NotethatthereisnosemicolonintheRustfunction,otherwiseitwouldreturnvoid Rustcurrentlyhasnonamedargumentsordefaultarguments(likePythonandTypeScript) anddoesnotallowmethodoverloading(likeC++,JavaorTypeScript). Innerfunctions Rustalsosupportsinnerfunctions. const RE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/; function isValidRange(start, end) { function isValid(date) { return date && date.match(RE); } return isValid(start) && isValid(end); } use regex::Regex; const RE: &str = r"^[0-9]{4}-[0-9]{2}-[0-9]{2}$"; fn is_valid_range(start: &str, end: &str) -> bool { fn is_valid(date: &str) -> bool { !date.is_empty() && Regex::new(RE) .unwrap() .is_match(date) } is_valid(start) && is_valid(end) } NotethatRegex isanexternalcrateandnotpartofRust’sstandardlibrary Extensionmethods Rust(likeKotlin)alsosupportsextensionmethods,sothepreviousexamplecouldbe rewrittenusingextensions. typealias Range = Pair<String, String> fun Range.isValid(): Boolean { val (start, end) = this Rust JavaScript Rust Kotlin ↑
  • 6.
    return start.isNotEmpty() &&end.isNotEmpty() } object Main { @JvmStatic fun main(args: Array<String>) { val range = Range("2020-01-01", "2020-12-31") if (range.isValid()) { println("Range is valid!") } } } type Range<'r> = (&'r str, &'r str); trait IsValid { fn is_valid(&self) -> bool; } impl<'r> IsValid for Range<'r> { fn is_valid(&self) -> bool { let (start, end) = &self; !start.is_empty() && !end.is_empty() } } fn main() { let range = ("2020-01-01", "2020-12-31"); if range.is_valid() { println!("Range is valid!"); } } InRust,extensionmethodsareaddedbyimplementingatrait.Whenthereisonlyonemethod,it’scommon tonamethetraitlikethemethod(IsValid).The'r denotesalifetime,formoreinformation,seethis sectionfromtheRustbook Closures(lambdas) Rustsupportsclosures(alsocalledLambdas,arrowfunctionsoranonymousfunctionsin otherlanguages). Whenaccessingvariablesfromoutsidetheclosure,RustismorestrictthanJavaScriptor Java,seecapturingformoredetails. function findEmails(list) { return list.filter( s => s && s.includes("@") Rust JavaScript ↑
  • 7.
    ); } fn find_emails(list: Vec<String>)-> Vec<String> { list.into_iter() .filter(|s| s.contains("@")) .collect() } Formorefilterexamples,seethissectionfromthedocumentation Expressions InRust,almosteverythingisanexpression,likeinKotlinanddifferentfromJavaScriptor Java.Youcandirectlyassigntheresultofan if statementtoavariable,forexample. function getLogLevel() { let level = process.env.TRACE ? "trace" : process.env.DEBUG ? "debug" : "info"; level = level === "trace" ? 0 : level === "debug" ? 1 : 2; console.log("using log level", level); return level; } fn get_log_level() -> u32 { let level = if std::env::var("TRACE").is_ok() { "trace" } else if std::env::var("DEBUG").is_ok() { "debug" } else { "info" }; let level = match level { "trace" => 0, "debug" => 1, _ => 2, Rust JavaScript Rust ↑
  • 8.
    }; println!("using log level{}", level); level } TheRustcodeusesmatch,whichislikeswitch inJavaorJavaScriptexceptthatit’sanexpressionand providesmoreflexibility.Unlessmanyotherlanguages,Rustallowsvariableshadowingforlocalvariables (level inthisexample) Structs(classes) RustdoesnothavefullsupportforclasseslikeJavaorTypeScript,butinsteadoffersstructs (similartostructsinC).Thesearelikedatacontainerswithmethods,buttheydon’tsupport alloftheobjectorientedconcepts,likeinheritance. public class HttpClient { private final ClientImpl clientImpl; public HttpClient() { clientImpl = new ClientImpl(); } public String get(String url) { return clientImpl.newRequest() .get(url).asString(); } } public static void main(String[] args) { HttpClient httpClient = new HttpClient(); System.out.println(httpClient .get("https://example.com/")); } pub struct HttpClient { client_impl: ClientImpl, } impl HttpClient { pub fn new() -> HttpClient { HttpClient { client_impl: ClientImpl {}, } } pub fn get(&self, url: &str) -> String { self.client_impl.new_request() Java Rust ↑
  • 9.
    .get(url) .as_string() } } fn main() { lethttp_client = HttpClient::new(); println!("{}", http_client.get("https://example.com/")); } InJava,mutabilityisgivenonafieldlevel,hereclientImpl isimmutable.InRust,themutabilitymodifieris setontheinstancevariableandnotperfield,soyoucannothavemutableandimmutablefieldsinthe samestruct(seetheInteriorMutabilityPattern) Methodsdonothaveimplicitaccesstothis,asinJavaorJavaScript,soyouneedtopassan explicitargumentnamed self,likeinPython.Methodswithoutthisparameterarecalled associatedfunctions(theyarecalledstaticmethodsinsomelanguages). InRust,thereisnoconstructor,structsarecreatedsimilarlytoobjectsinJavaScript.For caseswhereconstructorlogicisrequired,thereisaconventiontocreateanassociated function(staticmethod)named new,whichwillreturntheconstructedobjectinstance. Traits(interfaces) ThemostsimilarthingtointerfacesinRustaretraits. interface Named { fun name(): String } data class User( val id: Int, val name: String ) : Named { override fun name(): String { return this.name } } pub trait Named { fn name(&self) -> &String; } pub struct User { pub id: i32, pub name: String, } Kotlin Rust ↑
  • 10.
    impl Named forUser { fn name(&self) -> &String { return &self.name; } } Rustismostlyastaticlanguage,sosomethingsthatotherlanguagewilldoduringruntime, Rustwilldoduringcompiletime,whenpossible.Interfacesareusuallyusedfordynamic dispatchandifyouwanttousetraitsinasimilarway,seethischapteraboutstaticand dynamicdispatchandthisblogpost. Defaultmethods Traitsalsosupportdefaultmethods,likeinterfacesinJavaorKotlin. Associatedfunctions InRust,traitscanalsohaveassociatedfunctions,forexample from_str in std::str::FromStr (sectionstringparsingintheRustCookbook) interface FromList<T> { fun fromList(list: List<Int>): T? } data class MyPoint(val x: Int, val y: Int) { companion object : FromList<MyPoint> { override fun fromList(list: List<Int>): MyPoint? { return when (list.size) { 2 -> MyPoint(list[0], list[1]) else -> null } } } } object Main { @JvmStatic fun main(args: Array<String>) { val point = MyPoint.fromList(listOf(100, 200)) println(point) } } trait FromList<T> { fn from_list(list: &Vec<i32>) -> Option<T>; } struct MyPoint { Kotlin Rust ↑
  • 11.
    x: i32, y: i32, } implFromList<Self> for MyPoint { fn from_list(list: &Vec<i32>) -> Option<Self> { match list.len() { 2 => Some(MyPoint { x: list[0], y: list[1], }), _ => None, } } } fn main() { let point = MyPoint::from_list(&vec![100, 200]).unwrap(); println!("({}, {})", point.x, point.y); } FormoreinformationabouttheOption type,seetheNullvaluessectionbelow.ThekeywordSelf (upper case)canbeusedtoreferencethecurrenttype Enums Rustsupportsenums,likeJavaorKotlin,butwithmoreflexibility.TheRustenumsoffermore thaninC++orTypeScript,astheyarenotmerelyalistofconstants,butmorelikeunions. enum UserRole { RO("read-only"), USER("user"), ADMIN("administrator"); private final String name; UserRole(String name) { this.name = name; } String getName() { return name; } boolean isAccessAllowed(String httpMethod) { switch (httpMethod) { case "HEAD": case "GET": return true; Java ↑
  • 12.
    case "POST": case "PUT": returnthis == USER || this == ADMIN; case "DELETE": return this == ADMIN; default: return false; } } } class Main { public static void main(String[] args) { UserRole role = UserRole.RO; if (role.isAccessAllowed("POST")) { System.out.println("OK: " + role.getName()); } else { System.out.println("Access denied: " + role.getName()); } } } #[derive(PartialEq)] enum UserRole { RO, USER, ADMIN, } impl UserRole { fn name(&self) -> &str { match *self { UserRole::RO => "read-only", UserRole::USER => "user", UserRole::ADMIN => "administrator", } } fn is_access_allowed( &self, http_method: &str, ) -> bool { match http_method { "HEAD" | "GET" => true, "POST" | "PUT" => { *self == UserRole::USER Rust ↑
  • 13.
    || *self ==UserRole::ADMIN } "DELETE" => *self == UserRole::ADMIN, _ => false, } } } fn main() { let role = UserRole::RO; if role.is_access_allowed("POST") { println!("OK: {}", role.name()); } else { println!("Access denied: {}", role.name()); } } Rustdoesnotsupportconstantsinenums,soweneedtouseamatchinthename method.Theenum matchesarecheckedatcompiletime,sowhenallenumvariantsareused,thereisnoneedtohavea defaultbranch. Formoreinformationaboutthe#[derive(PartialEq)] line,seethesectionaboutAttributes Associatedvalues Rustenumsalsosupportassociatedvalues,whichmeanstheyarenotconstant,butinstead allowthecreationofenumvariantinstanceswithspecificvalues. sealed class GitCommand { abstract fun execute() } object Status : GitCommand() { override fun execute() = executeCommand(listOf("status")) } class Checkout( private val branch: String ) : GitCommand() { override fun execute() = executeCommand(listOf("checkout", branch)) } class Add( private val files: List<String> ) : GitCommand() { override fun execute() = executeCommand(listOf("add") + files) } Kotlin ↑
  • 14.
    class Log( private valdecorate: Boolean, private val patch: Boolean ) : GitCommand() { override fun execute() { val args = mutableListOf("log") if (decorate) { args.add("--decorate") } if (patch) { args.add("--patch") } executeCommand(args) } } fun executeCommand(args: List<String>) { val redirect = ProcessBuilder.Redirect.INHERIT ProcessBuilder(listOf("git") + args) .redirectInput(redirect) .redirectOutput(redirect) .redirectError(redirect) .start() .waitFor() } object Main { @JvmStatic fun main(args: Array<String>) { val command = Log(decorate = false, patch = true) command.execute() } } use std::process::Command; pub enum GitCommand<'g> { STATUS, CHECKOUT(&'g str), ADD(Vec<&'g str>), LOG { decorate: bool, patch: bool }, } impl<'g> GitCommand<'g> { fn execute(self) { let args: Vec<&str> = match self { GitCommand::STATUS => vec!["status"], GitCommand::CHECKOUT(branch) => { vec!["checkout", branch] Rust ↑
  • 15.
    } GitCommand::ADD(files) => { [vec!["add"],files].concat() }, GitCommand::LOG { decorate, patch, } => { let mut args = vec!["log"]; if decorate { args.push("--decorate") } if patch { args.push("--patch") } args } }; execute_command("git", &args); } } fn execute_command(command: &str, args: &[&str]) { Command::new(command) .args(args) .spawn() .expect("spawn failed!") .wait() .expect("command failed!"); } fn main() { let command = GitCommand::LOG { decorate: false, patch: true, }; command.execute(); } SealedClassesinKotlinaresimilartoRust’senums,astheyallowthemodellingofaclosedtypehierarchy Seethissectionaboutenumsformoreexamples. Concepts Ownership OnethingthatisveryspecialaboutRustisthewayithandlesmemoryallocations:Itdoesn’t haveagarbagecollector(likeJavaScript,JavaorGo),butthedeveloperdoesnotneedto ↑
  • 16.
    freememoryexplicitly,either(likeinC).Instead,Rustautomaticallyfreesmemorywhenitis nolongerinuse.Forthismechanismtowork,thedeveloperneedstoexplicitlythinkabout ownershipofthevaluestheprogramisusing. class User { privateString name; public User(String name) { this.name = name; } } public static void main(String[] args) { String name = "User"; User user1 = new User(name); User user2 = new User(name); } struct User { name: String, } fn main() { let name = String::from("User"); let user1 = User { name }; let user2 = User { name }; // compile error } InJava,thegarbagecollectorwillregularlychecktheobjectreferencesandwhennothingreferencesthe Userinstancesanymore,theywillbedeleted.Oncebothinstanceshavebeendetectedasunused,the "User" stringcanalsobedeleted. InRust,valuescanonlybeownedbyoneobjectatatime:theassignmenttouser1 isOK,becausethe valuewillbemovedfromthename variabletouser1.Thecreationofuser2,however,willgiveacompile error,becausethestring"User" isnowownedbyuser1,notbyname. InC,thedeveloperhastomakesurethatallocatedmemoryisfreedwhenitisnolonger needed.Thiscanleadtobugs(memoryleaks)andwasoneofthemotivationforRust’s differentapproachtomanagingmemory.WhileRustoffersprotectionsagainstit,it’snot impossibletohavememoryleaks. #include <string> std::string* get_string() { std::string* string = new std::string("hello"); delete string; Java Rust C++ ↑
  • 17.
    return string; } fn get_string()-> String { let string = String::from("hello"); drop(string); return string; // compile error! } Inthisexample,theC++codehasauseafterfreeerror.Notethatthisisonlyanexample,dropisrarely usedinRustcode(valueswillbedroppedautomaticallywhentheygooutofscope) Rust’sownershipmodelalsohelpswhendealingwithmulti-threadedcode.Thecompiler keepstrackofthevaluestheprogramisusingandmakessurethatthesamevalueisnot accessedfrommultiplethreadswithoutproperlockinglogicaroundit. #include <vector> #include <thread> int main() { std::vector<std::string> list; auto f = [&list]() { for (int i = 0; i < 10000; i++) { list.push_back("item 123"); } }; std::thread t1(f); std::thread t2(f); t1.join(); t2.join(); } use std::thread; fn main() { let mut list = vec![]; let f = move || { for _ in 0..10000 { list.push("item 123"); } }; Rust C++ Rust ↑
  • 18.
    let t1 =thread::spawn(f); let t2 = thread::spawn(f); // compile error! t1.join().unwrap(); t2.join().unwrap(); } Thestd::vector classisnotthread-safeandtheC++programwillcompilewithouterrors,butwhen running,itwillprobablycrashwithanerrorlikepointer being freed was not allocated orsimilar.In Rust,theclosuref takesownershipoflist (indicatedbythemovekeyword),that’swhythecompilergives anerrorwhenf isusedmorethanonce. Strings Rusthasmultiplestringtypes,themostimportantonesarestr(usuallyintheformof &str) andString. The &str typeonlyreferencesborrowedcontent,sothisisthetypethatisusedforstatic stringsandwhenreferencingstringslices.Likeotherimmutablereferences, &str values cannotbemodified.Use String ifyouneedamodifiablestring. const FILE_DATE = "2020-01-01"; function printCopyright() { const year = FILE_DATE.substr(0, 4); const copyright = `(C) ${year}`; console.log(copyright); } const FILE_DATE: &str = "2020-01-01"; fn print_copyright() { let year: &str = &FILE_DATE[..4]; let copyright: String = format!("(C) {}", year); println!("{}", copyright); } Seethissectionaboutstringsformoreexamples The String typeisusedfordynamicallycreatedstrings,whichcanbemodifiedandwhere thelengthisnotfixedatcompiletime. Usually, &str willbeusedforfunctionparametersand String willbeusedasreturnvalue. public static String repeat(String s, int count) { StringBuilder result = new StringBuilder(); for (int i = 0; i < count; i++) { result.append(s); } JavaScript Rust Java ↑
  • 19.
    return result.toString(); } fn repeat(s:&str, count: u32) -> String { let mut result = String::new(); for _ in 0..count { result += s; } result } Thisisjustanexample,RustalreadyhasString.repeat Forstructfields,usually String shouldbeused,asthevaluewillprobablybeownedbythat struct,especiallywhendealingwithlongerlivingobjects. Forstringconstants, &'static str canbeusedforfieldsinstead. data class User( private val source: String, private val name: String, private val address: String ) { companion object { fun fromString(string: String): User { val lines = string.lines() return User("kotlin-v1.0", lines[0], lines[1]) } } } pub struct User { source: &'static str, name: String, address: String, } impl User { pub fn new(s: &str) -> User { let mut lines = s.lines(); User { source: "rust-v1.0", name: lines.next().unwrap().to_owned(), address: lines.next().unwrap().to_owned(), } } } Rust Kotlin Rust ↑
  • 20.
    Notethatdefaultfieldaccessisprivate inRust Toconvertavariable s1oftype String to &str,use &s1. Toconvertavariable s2 oftype &str to String,use s2.to_owned() (thisallocatesnew memoryandcreatesacopyofthestring).Sometimesthisconversionisalsonecessaryfor literals,like "string literal".to_owned(). Nullvalues Rustdoesnothaveaspecial null value(alsocalled None or nil insomelanguages). Instead,thereistheOptionenum,whichisverysimilartotheOptionaltypeinJava. public static Integer getYear(String date) { if (date.length() >= 4) { String s = date.substring(0, 4); try { return Integer.valueOf(s); } catch (NumberFormatException e) { return null; } } else { return null; } } public static void main(String[] args) { Integer year = getYear("2020-01-01"); if (year != null) { System.out.println(year); } } fn get_year(date: &str) -> Option<u32> { if date.len() >= 4 { let s = date[..4]; match s.parse() { Ok(year) => Some(year), Err(_) => None } } else { None } } fn main() { if let Some(year) = get_year("2020-01-01") { println!("{}", year); Java Rust ↑
  • 21.
    } } TheOption typeisjustaregularenumfromRust’sstandardlibrary,withthetwoentriesNone andSome. WhenreturningOption,empty(null)valuescanbereturnedasNoneandnon-emptyvaluesneedtobe wrapped,likeSome(year) Tosimplycheckifavalueis null,withoutneedingtogettheactualvalue, is_none() can beused. Integer year = getYear(""); if (year == null) { System.err.println("Invalid date given!"); } let year: Option<u32> = get_year(""); if year.is_none() { println!("Invalid date given!"); } Sometimesit’susefultorunsomecodeonlyifavalueis null,toensureavariablehasa non-null value.TherearemanywaystodoitinRust,butusing match offersthemost concisesyntax. String url = "https://github.com"; String content = cache.get(url); if (content == null) { content = loadUrl(url); } let url = "https://github.com"; let content = match cache.get(url) { Some(content) => content, None => load_url(url), }; NotethatintheRustcode,content isimmutable(toachievethesameinJava,wewouldneedtointroduce anotherlocalvariableoranewmethod) Errorhandling RustdoesnotofferexceptionslikeC++,JavaorJavaScript.Instead,errorconditionsare indicatedviathemethod’sregularreturnvalue,likeinCorGo. Java Rust Java Rust ↑
  • 22.
    package main import ( "fmt" "log" "strconv" ) funcParsePort(port string) (uint16, error) { p, err := strconv.ParseUint(port, 10, 16) if err != nil { return 0, err } if p == 0 { return 0, fmt.Errorf("invalid: %d", p) } return uint16(p), nil } func main() { port, err := ParsePort("123") if err != nil { log.Fatalf("failed to parse port: %v", err) } log.Printf("port: %d", port) } use std::error::Error; fn parse_port(s: &str) -> Result<u16, Box<Error>> { let port: u16 = s.parse()?; if port == 0 { Err(Box::from(format!("invalid: {}", port))) } else { Ok(port) } } fn main() { match parse_port("123") { Ok(port) => println!("port: {}", port), Err(err) => panic!("{}", err), } } InRustit’scommontohavestatementswhichcoverallpossiblecases,eitherusingif/else ormatch,so youarelesslikelytoencounteranearlyreturn inRustthaninGo Go Rust ↑
  • 23.
    Inthepreviousexample,weneededtouse Box<Error>,becausethereturnederrortype cannotbedeterminedduringcompiletime:Itwilleithercontainaninstanceof std::num::ParseIntError (fromtheparsemethod,whenparsingfails),orastring(when theportiszero). The? intheline let port: u16 = s.parse()? isthe?operator:when parse returnedan error, parse_port willreturnthaterror,otherwisetheunwrappedresultof parse willbe assignedto port. Input Rustisoftenusedforterminalapplications,soitalsosupportsreadinginput.Therelevant codeisinthestd::iomodule. const { stdin } = require("process"); function readStr() { return new Promise((resolve, reject) => { let result = ""; stdin.setEncoding("utf8"); stdin.on("readable", () => { let chunk; while ((chunk = stdin.read())) { result += chunk; } }); stdin.once("error", err => reject(err)); stdin.once("end", () => resolve(result)); }); } use std::io::{stdin, Read}; fn read_str() -> Option<String> { let mut buffer = String::new(); match stdin().read_to_string(&mut buffer) { Ok(_) => Some(buffer), Err(err) => { eprintln!( "Error while reading input: {}", err ); None } JavaScript Rust ↑
  • 24.
    }; } NotethattheJavaScriptcodereturnsaPromise,soit’sasynchronous,whiletheRustexamplecodeis synchronous Attributes(annotations) Rustalsosupportsattributes(alsocalledannotationsinotherlanguages).Theyare interpretedduringcompiletimeandarecomparabletopreprocessormacrosinCorC++. #include <cstdint> [[nodiscard]] int32_tadd(int32_t a, int32_t b) { return a + b; } int main() { add(1, 2); // warning: unused return value } #[must_use] fn add(a: i32, b: i32) -> i32 { a + b } fn main() { add(1, 2); // warning: unused return value } Averycommonattributeisderive,whichcanbeusedtoquicklyimplementtraits.Itisoften usedlike #[derive(Debug, PartialEq)],toimplementtheDebugandPartialEqtraits. Miscellaneous Packagemanagement RustusuallyusestheCargopackagemanager,whichislikenpmoryarnforJavaScriptor MavenforJava. Thepackageregistryisavailableatcrates.io. Projectsetup IfyouhaveusedESLintbeforeforJavaScriptorTypeScript,rust-clippyisasimilartoolfor Rust,whichdetectscommonmistakesandbugs. C++ Rust ↑
  • 25.