Rust how different wrt python?

Amay B
CoderHack.com
Published in
5 min readSep 15, 2023
Photo by Nikola Johnny Mirkovic on Unsplash

I. Closures

A closure is an anonymous function that captures its environment. It allows you to save some local state and encapsulate it within a function.

Example: A simple closure

fn main() {
let closure = |x| x + 1;

let result = closure(2);
println!("Result: {}", result);
}

This closure simply adds 1 to its argument. When we call it with 2, it returns 3.

Capturing variables: Closures capture variables from the surrounding scope

Closures can capture variables from the scope they are defined in:

fn main() {
let greeting = "Hello";
let closure = || println!("{}", greeting);
closure();
}

Here, the closure uses the greeting variable from the surrounding scope. It prints “Hello” when called.

Closures as arguments: Passing closures as arguments to higher-order functions

We can pass closures as arguments to functions to use them as callbacks. For example:

fn call_with_one(closure: impl Fn(i32)) {
closure(1);
}

fn main() {
call_with_one(|x| println!("{}", x));
}

Here we define a function call_with_one() that calls a closure with 1. We pass it a closure that prints its argument, so it will print 1.

II. Iterators and Iterables

Iterators allow you to perform some task on a sequence of items in turn. An iterable is any collection that can return an iterator.

Defining an iterator: The Iterator trait

To define an iterator, we implement the Iterator trait:

struct Counter {
count: u32
}

impl Iterator for Counter {
type Item = u32;

fn next(&mut self) -> Option<Self::Item> {
self.count += 1;

if self.count < 6 {
Some(self.count)
} else {
None
}
}
}

This is an iterator that counts from 1 to 5.

Consuming an iterator: The next method

We call the next() method on an iterator to get each element:

let mut counter = Counter { count: 0 };

assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), Some(4));
assert_eq!(counter.next(), Some(5));
assert_eq!(counter.next(), None);

Iterator adaptors: Methods that consume an iterator and return a new iterator

Rust provides many iterator adaptors, like:

  • map: Applies a function to each element
  • filter: Filters out elements
  • sum: Sums all elements
  • any: Checks if any element satisfies a predicate
  • all: Checks if all elements satisfy a predicate
  • etc.

We can chain multiple adaptors together!

Lazy evaluation: Iterators are lazy, meaning they compute elements only when needed

Iterators don’t compute their elements upfront. They wait until you call next() before calculating the next element. This is known as lazy evaluation and it allows us to create infinite iterators!

III. Higher-order functions

A higher-order function is a function that takes functions as arguments or returns a function.

Map: Applies a function to all elements of an iterator

The map() method applies a closure to each element of an iterator and returns a new iterator with the results:

fn main() {
let nums = vec![1, 2, 3];
let doubled = nums.iter().map(|x| x * 2);

for num in doubled {
println!("{}", num);
}
}

This will print: 2 4 6

Filter: Filters out elements of an iterator

The filter() method applies a closure to each element of an iterator and returns a new iterator with only the elements where the closure returns true:

fn main() {
let nums = vec![1, 2, 3, 4, 5];
let even = nums.iter().filter(|x| x % 2 == 0);

for num in even {
println!("{}", num);
}
}

This will print: 2 4

Fold/Reduce: Folds an iterator to a single value

The fold() method applies a closure sequentially on each element of an iterator, aggregating the result into a final value:

fn main() {
let nums = vec![1, 2, 3];
let sum = nums.iter().fold(0, |total, num| total + num);

println!("The sum is: {}", sum);
}

This will print: The sum is: 6

Example: Summing all elements of an iterator

We can sum all elements of an iterator like this:

fn main() {
let nums = vec![1, 2, 3, 4, 5];
let sum = nums.iter().sum();

println!("The sum is: {}", sum);
}

This will print: The sum is: 15

IV. Traits

Traits define shared behavior that can be implemented by multiple types. They allow abstraction over multiple types with common behavior.

Defining a trait: The trait keyword

We define a trait using the trait keyword:

trait Hello {
fn say_hello(&self);
}

This defines a trait Hello with one method say_hello().

Implementing a trait: The impl keyword

We implement a trait for a type using impl:

struct EnglishGreeter {}

impl Hello for EnglishGreeter {
fn say_hello(&self) {
println!("Hello!");
}
}

This implements the Hello trait for the EnglishGreeter struct.

Using trait methods: Calling methods of a trait

We can call the methods of a trait on any type that implements it:

fn main() {
let greeter = EnglishGreeter {};
greeter.say_hello();
}

This will print: Hello!

Default methods: Providing default method implementations for traits

We can provide default method implementations in traits that can be overridden by implementors:

trait Hello {
fn say_hello(&self) {
println!("Hello!");
}
}

Now any type implementing Hello just needs to implement say_hello() if it wants to override the default.

Traits as parameters: Using traits as parameters (bounds)

We can use traits as bounds to constrain generic type parameters to types that implement a trait:

fn say_hello<T: Hello>(greeter: T) {
greeter.say_hello();
}

fn main() {
let english_greeter = EnglishGreeter {};
say_hello(english_greeter);
}

Here, the generic T is bounded by the Hello trait, so only types that implement Hello can be used as arguments to say_hello().

V. Pattern Matching

Pattern matching allows us to match values against patterns to extract information from the values.

Matching literals: Matching against literal values

We can match against literal values:

fn main() {
let x = 1;

match x {
1 => println!("one"),
2 => println!("two"),
_ => println!("anything else"),
}
}

This will print one.

Matching enum variants: Matching against enum variants

We can match against enum variants to handle each case specifically:

enum Color {
Red,
Blue,
Green
}

fn main() {
let color = Color::Red;

match color {
Color::Red => println!("The color is red!"),
Color::Blue => println!("The color is blue!"),
Color::Green => println!("The color is green!"),
}
}

This will print The color is red!.

Matching tuples: Matching against tuples

We can match against tuples to destructure their elements:

fn main() {
let tuple = (1, 2);

match tuple {
(1, 1) => println!("the tuple contains 1s"),
(1, _) => println!("the tuple has a 1"),
(_, 2) => println!("the tuple has a 2"),
_ => println!("anything else"),
}
}

This will print the tuple has a 2.

--

--