more work on the Rust c++ comparison
This commit is contained in:
parent
4bcfdeb435
commit
07ef4651c2
1 changed files with 203 additions and 15 deletions
|
@ -23,12 +23,14 @@ TODO: make these links
|
|||
- Hello World
|
||||
- Variables
|
||||
- Copying and Moving
|
||||
- Basic Types
|
||||
- Basic/Primitive Types
|
||||
- Common Stdlib Types
|
||||
- Loops
|
||||
- Functions
|
||||
- Making Your Own Types
|
||||
- Type Aliasing
|
||||
- Compile time computation
|
||||
- Pattern Matching
|
||||
- Generics
|
||||
- Modularity
|
||||
- Dependency Management and Build Systems
|
||||
|
@ -152,6 +154,9 @@ std::vector<std::unique_ptr<int>> second = std::move(first);
|
|||
second = std::move(first)
|
||||
```
|
||||
|
||||
Doing making a type move only manually involves deleting the lvalue reference constructor and assignment operators and overriding the rvalue reference constructor and assignment operators.
|
||||
For those in need of more info looking into the rule of 5 will help.
|
||||
|
||||
### Rust
|
||||
|
||||
In Rust we have the opposite phenomenon, a value will be moved if you don't take action to prevent it.
|
||||
|
@ -185,54 +190,237 @@ let second = first.clone();
|
|||
let third = first;
|
||||
```
|
||||
|
||||
## Basic Types
|
||||
## Basic/Primitive Types
|
||||
|
||||
Nothing interesting, here's a table providing info on equivalent types.
|
||||
While C++ literals and Rust literals and how they differ are mildly interesting, I haven't given them a sections.
|
||||
At the type level though there's nothing interesting, so here's a table providing info on equivalent types.
|
||||
|
||||
If a C++ type has `std::` at the front it isn't a primitive.
|
||||
Note: If a C++ type has `std::` at the front it isn't a primitive type.
|
||||
|
||||
Note2: if you see something like `asdf(1,2,3,4)` that just means I don't want to make 4 rows in the table for `asdf1`, `asdf2`, `asdf3` and `asdf4`.
|
||||
The 3 dots serve the same purpose for `unsigned` because words are long.
|
||||
|
||||
Note3: Sometimes I will a single capital letter, like `T` as a placeholder for templates/generics.
|
||||
|
||||
|C++|Rust|
|
||||
|-|-|
|
||||
|`bool`|`bool`|
|
||||
|`char`|no equivalent|
|
||||
|`char`|equivalent is C++ implementation dependant|
|
||||
|`signed char`|`i8`|
|
||||
|`unsigned char`|`u8`|
|
||||
|`short`|no equivalent `i16` is the closest|
|
||||
|`int`|no equivalent `i32` is the closest|
|
||||
|`long`|no equivalent `i32` is the closest|
|
||||
|`long long`|no equivalent `i64` is the closest|
|
||||
|`unsigned ...`|`u...`|
|
||||
|`unsigned ...`|`u(8,16,32,64)`|
|
||||
|`std::int(8,16,32,64)_t` |`i(8,16,32,64)`|
|
||||
|`std::uint(8,16,32,64)_t`|`u(8,16,32,64)`|
|
||||
|no equivalent barring compiler extensions|`i128`|
|
||||
|no equivalent barring compiler extensions|`u128`|
|
||||
|`float`|`f32`|
|
||||
|`double`|`f64`|
|
||||
|`long double`|no equivalent|
|
||||
|`&T`|`&mut T`|
|
||||
|`&const T` or `const &T`|`&T`|
|
||||
|`*T`|`*mut T`|
|
||||
|`*const T` or `const *T`|`*const T`|
|
||||
|`std::float16_t` (C++23)|`f16` (nightly at time of writing)|
|
||||
|`std::float128_t` (C++23)|`f128` (nightly at time of writing)|
|
||||
|`std::size_t`|`usize`|
|
||||
|`std::ptrdiff_t` (not touching `ssize_t` thanks)|`isize`|
|
||||
|`T[N]` or `std::array<T,N>`|`[T;N]`|
|
||||
|no equivalent `std::uint32_t` is the closest|`char`|
|
||||
|no equivalent `std::string_view` is the closest|`str`|
|
||||
|no equivalent `std::span<T>` is the closest|`[T]`|
|
||||
|`void`|no equivalent `()` is the closest|
|
||||
|no equivalent `void`, `std::monostate` and `std::tuple<>` are the closest|`()`|
|
||||
|no equivalent `[[noreturn]] void` is the closest | `!` (nightly at time of writing) |
|
||||
|
||||
## Common Stdlib Types
|
||||
|
||||
More equivalent types, this time from the stdlib, here's another table.
|
||||
|
||||
Note: (in prelude) just means you don't need the `std::blah` or a `use` to use the type.
|
||||
|
||||
Note2: Sometimes I will a single capital letter, like `T` as a placeholder for templates/generics.
|
||||
|
||||
|C++|Rust|
|
||||
|-|-|
|
||||
|`std::vector<T>`|`std::vec::Vec<T>` (in prelude)|
|
||||
|`std::span<T>`| `&[T]`|
|
||||
|`std::string`|`std::string::String` (in prelude) or `std::ffi::OSString`|
|
||||
|`std::string_view`|`&str`|
|
||||
|`std::optional<T>`|`std::option::Option<T>` (in prelude)|
|
||||
|`std::expected<T,E>`|`std::result::Result<T,E>` (in prelude)|
|
||||
|`std::unique_ptr<T>`|`alloc::boxed::Box<T>` (in prelude)|
|
||||
|`std::shared_ptr<T>`|`alloc::rc::Rc<T>`|
|
||||
|`std::weak_ptr<T>`|`alloc::rc::Weak<T>`|
|
||||
|
||||
## Loops
|
||||
|
||||
TODO
|
||||
### Commentary
|
||||
All of this is within some function body.
|
||||
|
||||
### C++
|
||||
### `while` loops
|
||||
|
||||
### Rust
|
||||
```cpp
|
||||
// C++
|
||||
while (some_condition) {
|
||||
//jump to next loop
|
||||
continue;
|
||||
// exit loop early
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
```rs
|
||||
//Rust
|
||||
while some_condition {
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
### `for` loops
|
||||
|
||||
Rust does not have a direct equivalent to C++ for loops of the form.
|
||||
|
||||
```cpp
|
||||
for(auto v = some_initial_value; condition; update()){
|
||||
// loop body
|
||||
}
|
||||
```
|
||||
|
||||
However it does have an equivalent to loops of the form
|
||||
|
||||
```cpp
|
||||
for(auto v:some_iterator){
|
||||
// loop body
|
||||
}
|
||||
```
|
||||
|
||||
namely
|
||||
|
||||
```rs
|
||||
for v in some_iterator {
|
||||
// loop body
|
||||
}
|
||||
```
|
||||
|
||||
Move by default and pattern matching can lead to potentially confusing loops however.
|
||||
|
||||
While on the subject of iterators though both languages have higher level constructs for working on iterators.
|
||||
There are a lot of these however and they are frequently named similarly so instead of making an incomplete table instead I'd like to write about how iterators differ between the 2 languages.
|
||||
|
||||
In C++ an iterator is an object which has overloads which allow it to be treated as a pointer.
|
||||
What this means in practice is that until the introduction of the ranges library in C++20 (which I haven't been able to use without getting a compiler error yet) that passing in an iterator to some function generally meant passing in the start and end point iterators.
|
||||
|
||||
In Rust things work differently, instead all iterators must fit a particular shape (see #Generics(TODO: hyperlink) for what that means) where they have a `next` method which returns an `Option` (see (#Common Stdlib Types) (TODO: hyperlink) for more info).
|
||||
This means that
|
||||
|
||||
1. The iterator can be passed as 1 value
|
||||
2. The iterator can be only partially evaluated
|
||||
|
||||
For comparison lets compare usage of C++ `std::transform` from the algorithms library to Rust's `map` method.
|
||||
|
||||
```cpp
|
||||
std::vector<int> bucket{};
|
||||
std::transform(some_iterator.begin(), some_iterator.end(), std::back_inserter(bucket), [](auto _){return 3;});
|
||||
```
|
||||
|
||||
Notice the need to have an explicit location to output things onto when we do the transform rather than when we need the values.
|
||||
|
||||
```rs
|
||||
let bucket:Vec<i32> = some_iterator
|
||||
.map(|_|{return 3;})
|
||||
.collect();
|
||||
```
|
||||
|
||||
This Rust version arguably does more than what is needed.
|
||||
If we had left things at just the usage of `map` it would functionally be a no-op, we wouldn't need somewhere to store our transformed data because we didn't make it yet.
|
||||
However to keep the 2 examples equivalent `collect` was used to gather things up into a `Vec`.
|
||||
|
||||
|
||||
C++20 ranges (with some C++23 additions) in principle allow for C++ code which is similar to this Rust code to be written like follows
|
||||
|
||||
```cpp
|
||||
namespace views = std::ranges::views;
|
||||
std::vector<int> bucket = some_iterator
|
||||
| views::transform([](auto _){return 3})
|
||||
| std::ranges::to<std::vector>();
|
||||
```
|
||||
|
||||
However I suffer from severe skill issue when using ranges so I haven't been able to write something like this off the cuff without running into a compiler error wall so I can't vouch for it.
|
||||
|
||||
## Functions
|
||||
|
||||
TODO
|
||||
### Commentary
|
||||
Basic functions in Rust and C++ are by and large very similar to each other in terms of semantics, they differ mostly in terms of syntax.
|
||||
|
||||
### C++
|
||||
A basic example with the 2 notable C++ syntax variants and the 2 ways of doing it in Rust is below.
|
||||
|
||||
### Rust
|
||||
```cpp
|
||||
|
||||
using i32 = std::int32_t;
|
||||
|
||||
i32 add(i32 a, i32 b) {
|
||||
return a+b;
|
||||
}
|
||||
auto add(i32 a, i32 b) -> i32 {
|
||||
return a+b;
|
||||
}
|
||||
```
|
||||
|
||||
```rs
|
||||
fn add(a:i32, b:i32) -> i32 {
|
||||
return a+b;
|
||||
}
|
||||
fn add(a:i32, b:i32) -> i32 {
|
||||
a+b
|
||||
}
|
||||
```
|
||||
|
||||
The difference in C++'s case is how the function is declared (basically syntax sugar) while in Rust's case the difference is due to there being multiple ways of deciding what value a function returns.
|
||||
|
||||
For Rust you can have a function return it's value via `return` however you can also have a function return it's value via having the last expression in the function body evaluate to the return type.
|
||||
|
||||
In addition to basic functions we also have closures/anonymous functions though.
|
||||
|
||||
The following demonstrates them without captures, capture by reference and capture by move.
|
||||
|
||||
```c++
|
||||
using i32 = std::int32_t;
|
||||
|
||||
std::unique_ptr<i32> x = std::make_unique(3);
|
||||
auto add = [](i32 a, i32 b){
|
||||
return a+b;
|
||||
};
|
||||
auto add_ref_x = [&x](i32 a, i32 b){
|
||||
return a+b+*x;
|
||||
};
|
||||
auto add_move_x = [x = std::move(x)](i32 a, i32 b){
|
||||
return a+b+*x;
|
||||
};
|
||||
```
|
||||
|
||||
```rs
|
||||
let x = Box::new(3);
|
||||
let add = |a:i32, b:i32| {
|
||||
return a+b;
|
||||
};
|
||||
let add_ref_x = |a:i32, b:i32| {
|
||||
return a+b+*x;
|
||||
};
|
||||
let add_mov_x = move |a:i32, b:i32| {
|
||||
return a+b+*x;
|
||||
};
|
||||
```
|
||||
|
||||
Again there isn't a meaningful substance difference though in C++'s case moving and referencing local values was a bit more tedious than it was in Rust which can be nice in some cases.
|
||||
In both cases the language just goes and makes an anonymous type for us which has the needed struct/class members and function call operator then constructs a value of that type with values from the surrounding function.
|
||||
|
||||
Well kinda, I'd link to a blog post going into detail on how things differ on a type level but I can't find it atm, sorry.
|
||||
|
||||
## Making Your Own Types
|
||||
|
||||
TODO
|
||||
### Commentary
|
||||
|
||||
### C++
|
||||
|
|
Loading…
Reference in a new issue