Skip to content

Commit

Permalink
Merge pull request #66 from aserebryakov/master
Browse files Browse the repository at this point in the history
References to modern C++ features.
  • Loading branch information
nrc committed Aug 16, 2017
2 parents 57e7a10 + 54bdab1 commit 64e500c
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 46 deletions.
11 changes: 9 additions & 2 deletions arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ println!("The second element is {}", a[1]);

You'll notice that array indexing is zero-based, just like C.

However, unlike C/C++, array indexing is bounds checked. In fact all access to
arrays is bounds checked, which is another way Rust is a safer language.
However, unlike C/C++<sup>[1](#1)</sup>, array indexing is bounds checked. In
fact all access to arrays is bounds checked, which is another way Rust is a
safer language.

If you try to do `a[4]`, then you will get a runtime panic. Unfortunately, the
Rust compiler is not clever enough to give you a compile time error, even when
Expand Down Expand Up @@ -266,3 +267,9 @@ elements, each with the value 42.
The initial value is not limited to integers, it can be any expression. For
array initialisers, the length must be an integer constant expression. For
`vec!`, it can be any expression with type `usize`.


##### 1

In C++11 there is `std::array<T, N>` that provides boundary checking when
`at()` method is used.
2 changes: 1 addition & 1 deletion control flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ fn print_some(x: i32) {
```

Another semantic difference is that there is no fall through from one arm to the
next.
next so it works like `if...else if...else`.

We'll see in later posts that match is extremely powerful. For now I want to
introduce just a couple more features - the 'or' operator for values and `if`
Expand Down
23 changes: 16 additions & 7 deletions data types.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ However, Rust enums are much more powerful than that. Each variant can contain
data. Like tuples, these are defined by a list of types. In this case they are
more like unions than enums in C++. Rust enums are tagged unions rather untagged
(as in C++), that means you can't mistake one variant of an enum for another at
runtime. An example:
runtime<sup>[1](#1)</sup>. An example:

```rust
enum Expr {
Expand Down Expand Up @@ -196,12 +196,12 @@ One particularly common enum in Rust is `Option`. This has two variants - `Some`
and `None`. `None` has no data and `Some` has a single field with type `T`
(`Option` is a generic enum, which we will cover later, but hopefully the
general idea is clear from C++). Options are used to indicate a value might be
there or might not. Any place you use a null pointer in C++ to indicate a value
which is in some way undefined, uninitialised, or false, you should probably use
an Option in Rust. Using Option is safer because you must always check it before
use; there is no way to do the equivalent of dereferencing a null pointer. They
are also more general, you can use them with values as well as pointers. An
example:
there or might not. Any place you use a null pointer in C++<sup>[2](#2)</sup>.
to indicate a value which is in some way undefined, uninitialised, or false,
you should probably use an Option in Rust. Using Option is safer because you
must always check it before use; there is no way to do the equivalent of
dereferencing a null pointer. They are also more general, you can use them with
values as well as pointers. An example:

```rust
use std::rc::Rc;
Expand Down Expand Up @@ -346,3 +346,12 @@ If you're using Cell/RefCell, you should try to put them on the smallest object
you can. That is, prefer to put them on a few fields of a struct, rather than
the whole struct. Think of them like single threaded locks, finer grained
locking is better since you are more likely to avoid colliding on a lock.


##### 1

In C++17 there is `std::variant<T>` type that is closer to Rust enums than unions.

##### 2

Since C++17 `std::optional<T>` is the best alternative of Option in Rust.
10 changes: 5 additions & 5 deletions destructuring 2.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ the destination must be annotated with `&`. For pass-by-value in Rust, there are
two further choices - copy or move. A copy is the same as C++'s semantics
(except that there are no copy constructors in Rust). A move copies the value
but destroys the old value - Rust's type system ensures you can no longer access
the old value. As examples, `int` has copy semantics and `Box<int>` has move
the old value. As examples, `i32` has copy semantics and `Box<i32>` has move
semantics:

```rust
Expand Down Expand Up @@ -112,7 +112,7 @@ passing rather than by-move).
```rust
enum Enum2 {
// Box has a destructor so Enum2 has move semantics.
Var1(Box<int>),
Var1(Box<i32>),
Var2,
Var3
}
Expand Down Expand Up @@ -173,9 +173,9 @@ OK, because now we are not dereferencing anywhere and thus not moving any part
of `x`. Instead we are creating a pointer which points into the interior of `x`.

Alternatively, we could destructure the Box (this match is going three levels
deep): `&Var1(box y) => {}`. This is OK because `int` has copy semantics and `y`
is a copy of the `int` inside the `Box` inside `Var1` (which is 'inside' a
borrowed reference). Since `int` has copy semantics, we don't need to move any
deep): `&Var1(box y) => {}`. This is OK because `i32` has copy semantics and `y`
is a copy of the `i32` inside the `Box` inside `Var1` (which is 'inside' a
borrowed reference). Since `i32` has copy semantics, we don't need to move any
part of `x`. We could also create a reference to the int rather than copy it:
`&Var1(box ref y) => {}`. Again, this is OK, because we don't do any
dereferencing and thus don't need to move any part of `x`. If the contents of
Expand Down
16 changes: 8 additions & 8 deletions destructuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ Last time we looked at Rust's data types. Once you have some data inside a struc
will want to get that data out. For structs, Rust has field access, just like
C++. For tuples, tuple structs, and enums you must use destructuring (there are
various convenience functions in the library, but they use destructuring
internally). Destructuring of data structures doesn't happen in C++, but it
might be familiar from languages such as Python or various functional languages.
The idea is that just as you can initialize a data structure by filling out its
fields with data from a bunch of local variables, you can fill out a bunch of
local variables with data from a data structure. From this simple beginning,
destructuring has become one of Rust's most powerful features. To put it another
way, destructuring combines pattern matching with assignment into local
variables.
internally). Destructuring of data structures exists in C++ only since C++17, so
it most likely familiar from languages such as Python or various functional
languages. The idea is that just as you can initialize a data structure by
filling out its fields with data from a bunch of local variables, you can fill
out a bunch of local variables with data from a data structure. From this
simple beginning, destructuring has become one of Rust's most powerful
features. To put it another way, destructuring combines pattern matching with
assignment into local variables.

Destructuring is done primarily through the let and match statements. The match
statement is used when the structure being destructured can have different
Expand Down
7 changes: 3 additions & 4 deletions hello world.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,9 @@ don't need to specify the type, it will be inferred for us.

Using `{}` in the `println!` statement is like using `%s` in printf. In fact, it
is a bit more general than that because Rust will try to convert the variable to
a string if it is not one already<sup>[1](#1)</sup>. You can easily play around
with this sort of thing - try multiple strings and using numbers (integer and
float literals will
work).
a string if it is not one already<sup>[1](#1)</sup> (like `operator<<()` in C++).
You can easily play around with this sort of thing - try multiple strings and
using numbers (integer and float literals will work).

If you like, you can explicitly give the type of `world`:

Expand Down
20 changes: 9 additions & 11 deletions primitives.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
# Primitive types and operators

TODO int/uint -> isize/usize

Rust has pretty much the same arithmetic and logical operators as C++. `bool` is
the same in both languages (as are the `true` and `false` literals). Rust has
similar concepts of integers, unsigned integers, and floats. However the syntax
is a bit different. Rust uses `int` to mean an integer and `uint` to mean an
is a bit different. Rust uses `isize` to mean an integer and `usize` to mean an
unsigned integer. These types are pointer sized. E.g., on a 32 bit system,
`uint` means a 32 bit unsigned integer. Rust also has explicitly sized types
`usize` means a 32 bit unsigned integer. Rust also has explicitly sized types
which are `u` or `i` followed by 8, 16, 32, or 64. So, for example, `u8` is an 8
bit unsigned integer and `i32` is a 32 bit signed integer. For floats, Rust has
`f32` and `f64`.

Numeric literals can take suffixes to indicate their type (using `i` and `u`
instead of `int` and `uint`). If no suffix is given, Rust tries to infer the
type. If it can't infer, it uses `int` or `f64` (if there is a decimal point).
instead of `isize` and `usize`). If no suffix is given, Rust tries to infer the
type. If it can't infer, it uses `isize` or `f64` (if there is a decimal point).
Examples:

```rust
fn main() {
let x: bool = true;
let x = 34; // type int
let x = 34u; // type uint
let x = 34; // type isize
let x = 34u; // type usize
let x: u8 = 34u8;
let x = 34i64;
let x = 34f32;
Expand Down Expand Up @@ -58,9 +56,9 @@ type. `as` cannot be used to convert between booleans and numeric types. E.g.,

```rust
fn main() {
let x = 34u as int; // cast unsigned int to int
let x = 10 as f32; // int to float
let x = 10.45f64 as i8; // float to int (loses precision)
let x = 34u as isize; // cast usize to isize
let x = 10 as f32; // isize to float
let x = 10.45f64 as i8; // float to i8 (loses precision)
let x = 4u8 as u64; // gains precision
let x = 400u16 as u8; // 144, loses precision (and thus changes the value)
println!("`400u16 as u8` gives {}", x);
Expand Down
41 changes: 33 additions & 8 deletions unique.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ fn foo() {
```

Here `x` is a pointer to a location on the heap which contains the value `75`.
`x` has type `Box<int>`; we could have written `let x: Box<int> = Box::new(75);`. This
is similar to writing `int* x = new int(75);` in C++. Unlike in C++, Rust will
tidy up the memory for us, so there is no need to call `free` or `delete`.
Unique pointers behave similarly to values - they are deleted when the variable
goes out of scope. In our example, at the end of the function `foo`, `x` can no
longer be accessed and the memory pointed at by `x` can be reused.
`x` has type `Box<isize>`; we could have written `let x: Box<isize> =
Box::new(75);`. This is similar to writing `int* x = new int(75);` in C++.
Unlike in C++, Rust will tidy up the memory for us, so there is no need to call
`free` or `delete`<sup>[1](#1)</sup>. Unique pointers behave similarly to
values - they are deleted when the variable goes out of scope. In our example,
at the end of the function `foo`, `x` can no longer be accessed and the memory
pointed at by `x` can be reused.

Owning pointers are dereferenced using the `*` as in C++. E.g.,

Expand Down Expand Up @@ -109,7 +110,7 @@ Likewise, if an owning pointer is passed to another function or stored in a
field, it can no longer be accessed:

```rust
fn bar(y: Box<int>) {
fn bar(y: Box<isize>) {
}

fn foo() {
Expand Down Expand Up @@ -147,7 +148,8 @@ fn bar(x: Box<Foo>, y: Box<Box<Box<Box<Foo>>>>) {

Assuming that the type `Foo` has a method `foo()`, both these expressions are OK.

Calling Box::new() with an existing value does not take a reference to that value, it copies that value. So,
Calling `Box::new()` with an existing value does not take a reference to that
value, it copies that value. So,

```rust
fn foo() {
Expand All @@ -165,3 +167,26 @@ this in more detail later.

Sometimes when programming, however, we need more than one reference to a value.
For that, Rust has borrowed pointers. I'll cover those in the next post.


##### 1

In C++11 the `std::unique_ptr<T>` was introduced that may be in some aspects
associated to Rust `Box<T>` but there are also significant differences.

`std::unique_ptr<T>` like `Box<T>` automatically releases the memory being
pointed once it goes out of the scope and has only move semantics.

In some way the `let x = Box::new(75)` may be interpreted as `const auto x =
std::unique_ptr<const int>{new int{75}};` in C++11 and `const auto x =
std::make_unique<const int>{75};` since C++14.

But there are still important differences between `Box<T>` and
`std::unique_ptr<T>` that should be taken into account:

1. If `std::unique_ptr<T>` is created by passing the pointer to constructor
it there is a possibility to have several unique pointers to the same memory
that is not possible with `Box<T>`
2. Once `std::unique_ptr<T>` is moved to another variable or to function
dereference of this pointer causes undefined behavior that is also
impossible in Rust

0 comments on commit 64e500c

Please sign in to comment.