Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix my comments in #40 #54

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/language/custom-types/members.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ impl Rectangle {
}
```

> Note: While in C# it is idiomatic to expose a property for every field and keep
> the fields private, in Rust it is more common to expose the fields directly when possible,
> since there is no syntax sugar for accessor methods and they also have complexities
> with the borrow checker.

## Extension Methods

Extension methods in C# enable the developer to attach new statically-bound
Expand Down Expand Up @@ -272,7 +277,7 @@ The table below is an approximation of the mapping of C# and Rust modifiers:

## Mutability

When designing a type in C#, it is the responsiblity of the developer to
When designing a type in C#, it is the responsibility of the developer to
decide whether the a type is mutable or immutable; whether it supports
destructive or non-destructive mutations. C# does support an immutable design
for types with a _positional record declaration_ (`record class` or `readonly
Expand Down
15 changes: 15 additions & 0 deletions src/language/equality.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ fn main() {
}
```

There is no direct equivalence to reference equality in Rust, since not everything is a reference.
However, when you have two references, you can check for their reference equality with `std::ptr::eq()`

```rust
fn main() {
let a = 1;
let b = 1;
println!("{}", a == b); // true
println!("{}", std::ptr::eq(&a, &b)); // false
println!("{}", std::ptr::eq(&a, &a)); // true
}
```

Another way to compare for reference equality is to convert the references to raw pointers and compare them using `==`.

See also:

- [`Eq`][eq.rs] for a stricter version of `PartialEq`.
Expand Down
3 changes: 3 additions & 0 deletions src/language/exception-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ The equivalent to the .NET `Exception.InnerException` property is the
implementation for `Error::source()`, the blanket (default) implementation
returns a `None`.

> Note: Unlike in C#, in Rust it is not strictly required for an error to implement `std::error::Error`
> (it can be used in `Result` even without that). But it is strongly encouraged, especially for public errors.

## Raising exceptions

To raise an exception in C#, throw an instance of the exception:
Expand Down
15 changes: 14 additions & 1 deletion src/language/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,25 @@ impl<T> Timestamped<T> {
}

impl<T> PartialEq for Timestamped<T>
where T: PartialEq {
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.value == other.value && self.timestamp == other.timestamp
}
}
```

A shortcut for `where` clauses that exists in Rust is constraining the parameters
directly at their declaration:
```rust
impl<T: PartialEq> PartialEq for Timestamped<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value && self.timestamp == other.timestamp
}
}
```
Although `where` clauses are somewhat more powerful, since they can constrain arbitrary types (e.g. `i32: PartialEq<T>`).

Generic type constraints are called [bounds][bounds.rs] in Rust.

Expand Down
46 changes: 41 additions & 5 deletions src/language/nullability-and-optionality.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,51 @@ if let Some(max) = max {
## Null-conditional operators

The null-conditional operators (`?.` and `?[]`) make dealing with `null` in C#
more ergonomic. In Rust, they are best replaced by using the [`map`][optmap]
method. The following snippets show the correspondence:
more ergonomic. In Rust, they are best replaced by using either the [`map`][optmap]
method or the [`and_then`][opt_and_then] method, depending on the nesting of the `Option`.
The following snippets show the correspondence:

```csharp
string? some = "Hello, World!";
string? none = null;
Console.WriteLine(some?.Length); // 13
Console.WriteLine(some?.Length); // Hello, World!
Console.WriteLine(none?.Length); // (blank)

record Name(string FirstName, string LastName);
record Person(Name? Name);

Person? person1 = new Person(new Name("John", "Doe"));
Console.WriteLine(person1?.Name?.FirstName); // John
Person? person2 = new Person(null);
Console.WriteLine(person2?.Name?.FirstName); // (blank)
Person? person3 = null;
Console.WriteLine(person3?.Name?.FirstName); // (blank)
```

```rust
let some: Option<String> = Some(String::from("Hello, World!"));
let none: Option<String> = None;
println!("{:?}", some.map(|s| s.len())); // Some(13)
println!("{:?}", some.map(|s| s.len())); // Some("Hello, World!")
println!("{:?}", none.map(|s| s.len())); // None

struct Name { first_name: String, last_name: String }
struct Person { name: Option<Name> }
let person1: Option<Person> = Some(Person { name: Some(Name { first_name: "John".into(), last_name: "Doe".into() }) });
println!("{:?}", person1.and_then(|p| p.name.map(|name| name.first_name))); // Some("John")
let person1: Option<Person> = Some(Person { name: None });
println!("{:?}", person1.and_then(|p| p.name.map(|name| name.first_name))); // None
let person1: Option<Person> = None;
println!("{:?}", person1.and_then(|p| p.name.map(|name| name.first_name))); // None
```

The `?` operator (mentioned in the previous chapter), can also be used to handle `Option`s.
It returns from the function with `None` if a `None` is encountered, or else continues with the
`Some` value:
```rust
fn foo(optional: Option<i32>) -> Option<String> {
let value = optional?;
Some(value.to_string())
}
```

## Null-coalescing operator
Expand Down Expand Up @@ -94,8 +124,14 @@ lazily initialize the default value.

The null-forgiving operator (`!`) does not correspond to an equivalent construct
in Rust, as it only affects the compiler's static flow analysis in C#. In Rust,
there is no need to use a substitute for it.
there is no need to use a substitute for it. [`unwrap`][opt_unwrap] is close,
though: it panics if the value is `None`. [`expect`][opt_expect] is similar but allows
you to provide a custom error message. Note that as previously said, panics should
be reserved to unrecoverable situations.

[option]: https://doc.rust-lang.org/std/option/enum.Option.html
[optmap]: https://doc.rust-lang.org/std/option/enum.Option.html#method.map
[opt_and_then]: https://doc.rust-lang.org/std/option/enum.Option.html#method.and_then
[unwrap-or]: https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or
[opt_unwrap]: https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap
[opt_expect]: https://doc.rust-lang.org/std/option/enum.Option.html#method.expect
13 changes: 11 additions & 2 deletions src/language/strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ There are differences in working with strings in Rust and .NET, but the
equivalents above should be a good starting point. One of the differences is
that Rust strings are UTF-8 encoded, but .NET strings are UTF-16 encoded.
Further .NET strings are immutable, but Rust strings can be mutable when declared
as such, for example `let s = &mut String::from("hello");`.
as such, for example `let mut s = String::from("hello");`.

There are also differences in using strings due to the concept of ownership. To
read more about ownership with the String Type, see the [Rust Book][ownership-string-type-example].
Expand All @@ -44,7 +44,7 @@ Rust:

```rust
let span: &str = "Hello, World!";
let str = Box::new("Hello World!");
let str: Box<str> = Box::from("Hello World!");
let mut sb = String::from("Hello World!");
```

Expand Down Expand Up @@ -116,6 +116,9 @@ let age = 42;
let str = format!("Person {{ name: {name}, age: {age} }}");
```

Note that `format!` only supports embedding variable names in the string; more complex
expressions are spelled like `format!("1 + 1 = {}", 1 + 1)`.

Custom classes and structs can also be interpolated in C# due to the fact that
the `ToString()` method is available for each type as it inherits from `object`.

Expand Down Expand Up @@ -159,6 +162,10 @@ let person = Person {
println!("{person}");
```

For converting values to string using `Display` without formatting, you can use
the `std::string::ToString` trait. Its `to_string()` method is equal to the `ToString()`
method in .NET, and implemented automatically whenever you implement `Display`.

Another option is to use the `std::fmt::Debug` trait. The `Debug` trait is
implemented for all standard types and can be used to print the internal
representation of a type. The following example shows how to use the `derive`
Expand All @@ -183,6 +190,8 @@ println!("{person:?}");

> Note: Using the :? format specifier will use the `Debug` trait to print the
> struct, where leaving it out will use the `Display` trait.
>
> You can also use the `:#?` specifier to pretty-print the debug format.

See also:

Expand Down
7 changes: 7 additions & 0 deletions src/threading/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,17 @@ A few things to note:
`data` must be copied or cloned (depending on what the type of the value
supports).

Since Rust 1.63.0, it is possible to use [scoped threads] to use non-static data
(including references to not-`move`d values) in threads. The trade-off is that
since the data must remain alive until the thread's end, it is forcibly joined
by the end of the scope.

- Rust thread can return values, like tasks in C#, which becomes the return
value of the `join` method.

- It is possible to also pass data to the C# thread via a closure, like the
Rust example, but the C# version does not need to worry about ownership
since the memory behind the data will be reclaimed by the GC once no one is
referencing it anymore.

[scoped threads]: https://doc.rust-lang.org/stable/std/thread/fn.scope.html