I have a struct with two constructors, one for public usage (pub fn new(...)) and one for internal usage (pub(crate) fn new_internal(...)). NB: I use the term "internal" in analogy to C# internal keyword, I don't know what the equivalent Rust term is. The difference between the two methods is given by a check on a &str parameter to verify if its value is a reserved keyword. This check must only occur in the pub(crate) constructor. My question is just for stylistic purposes: is there any naming convention for internal functions to distinguish them from their public variant? In my specific case, how should I name the internal constructor?
No, AFAIK there are no such conventions.
Generally Rust, due to its strong type system, is among the languages that have lesser need for naming conventions related to types, scope, etc., compared to some other languages.
Related
If I understand correctly rust unit-like structs can be used like say atoms in Erlang.
But I don't understand what zero-variant enums provide. Could someone explain what the main purpose of zero-variant enums is and in which cases they might be favored over unit-like structs?
One usage for zero-variants enum is to express unreachable code. For example, an infallible TryFrom or FromStr. This commonly occurs when using generics (here's an example: OnceCell has both get_or_init() and get_or_try_init() methods. To save code duplication, the get_or_init() method calls get_or_try_init(). However, without using empty enums, this would incur cost at runtime because of the panic for the impossible Err case if the get_or_try_init() call isn't inlined). This is intended to be replaced by the never type once stabilized. Using empty enums has two advantages over unit structs:
They cannot be constructed by mistake.
They can hint the optimizer that this code is unreachable and allow it to remove it. They can also help the developers avoid panics in the code, because an infallible enum may be converted into the never type by match value {}, and the never type may be coerced into any other type. An example is in the once_cell code above.
Another usage is in generics, when you need only a type and not value, for example in the Strategy pattern at compile time, some people prefer zero variants enums to express that this types are not meant to be instantiated.
When I'm using Rust's rand crate, if I want to produce a rand number, I would write:
use rand::{self, Rng};
let rand = rand::thread_rng().gen::<usize>();
If I don't use rand::Rng, an error occurs:
no method named gen found for struct rand::prelude::ThreadRng in the current scope
That's quite different from what I'm used to. Usually I treat mods like:
import rand from "path";
rand.generate();
Once I import the mod I don't need to import something else, and I can use every method it exports.
Why must I use rand::Rng to enable the gen method on rand::thread_rng()?
That's quite different from what I used to know.
It feels different because it is indeed different. You are probably used to dynamic dispatch via some kind of virtual method table (as in e.g. C++), or, in case of JS, to dynamic dispatch by looking up either the own properties of the receiver object, or its ancestors via the __proto__-chain. In any case, the object on which you are invoking a method carries around some data that tells it how to get the method that you're invoking. Given the signature of the invoked method, the receiver object itself knows how to get the method with that signature.
That's not the only way, though. For example,
modules / functors in OCaml or SML
Typeclasses in Haskell
implicits / givens in Scala
traits in Rust
work on a rather different principle: the methods are not tied to the receiver, but to the module / typeclass / given / trait instances. In each case, those are entities that are separate from the receiver of the method call. It opens some new possibilities, e.g. it allows you to do some ad-hoc polymorphism (i.e. to define instances of traits after the fact, for types that are not necessarily under your control). At the same time, the compiler typically requires a bit more information from you in order to be able to select the correct instances: it behaves somewhat like a little type-directed search engine, or even a little "theorem prover", and for this to work, you have to tell the compiler where to look for the suitable building blocks for those synthetically generated instances.
If you've never worked before with any language that has a compiler with a subsystem that is "searching for instances" based on type information, this should indeed feel quite foreign. The error messages and the solution approaches do indeed feel rather different, because instead of comparing your implementation against an interface and looking for conflicts, you have to guide this instance-searching mechanism by providing more hints (e.g. by importing more traits etc.).
In your particular case, rand::thread_rng returns a struct ThreadRng. On its own, the struct knows nothing about the gen method, because this method is not tied directly to the struct. Instead, it's defined in the Rng trait. But at the same time, it could be defined in some entirely unrelated trait, and have some completely different meaning. In order to disambiguate the intended meaning, you therefore have to explicitly specify that you want to work with the Rng trait. This is why you have to mention it in the use-clause.
I don't know the specific library you're using, but I can guess at the problem. I would guess that Rng is a trait which defines gen. Traits can be thought of as somewhat like Java's interfaces: they enable ad-hoc polymorphism by allowing you to define different behaviors for the same function on different datatypes.
However, Rust's traits fix one major problem (well, they fix several major problems, but one that's relevant here) with Java's interfaces. In Java, if you define an interface, then anyone writing a class can implement the interface, but you can't implement it for other people. In particular, the built-in types String and int and the like can never implement any new interfaces downstream. In Rust, either the trait writer or the struct/enum writer can implement the trait.
But this poses another issue. Now, if I have a value foo of type Foo and I write foo.bar(), then bar might not be a method defined on Foo; it might be something some trait writer implemented in some other file. We can't go search every Rust file on your computer for possible matching traits, so Rust makes the logical decision to restrict this search to traits that are in scope. If you want to call foo.bar() and bar is a method on trait Bar, then trait Bar has to be in scope when you call it. Otherwise, Rust won't see it.
So, in your case, thread_rng() returns a rand::prelude::ThreadRng. The method gen is not defined on rand::prelude::ThreadRng. Instead, it's defined on a trait called rand::Rng which is *implemented by ThreadRng. That trait has to be in-scope to use the method.
I have been following a Rust tutorial, that uses a custom type.
So a library was defined, that was used in a rust binary later via a cargo.toml dependency.
I ended up with a couple of issues:
E0368: A binary assignment operator like += or ^= was applied to a type that doesn't support it.
E0600: cannot apply unary operator '-'.
Does Rust:
necessitate Traits that provide these capability to be defined back in library code (money_typesafe below) referenced by the cargo.toml dependency (of main.rs) that gets used in the binary.
allow Traits to be applied in the using binary.
allow both 1) & 2)?
If 2, is permitted, does the syntax change on defining the Trait in any way?
cargo.toml
...
[dependencies]
money_typesafe = {path = "../../../2-Traits/10-Day-2-Assignment/day2assign/"}`
main.rs
...
use money_typesafe::currencies::{Money,GBP};
i.e. For 2) Can I add traits to Money or GBP in main.rs?
Footnote:
I did find the code for the tutorial on github. It followed the option 1 scenario. Not sure if other option exists extending capability of type in another place.
It involved:
AddAssign
Neg (I think the example give here isn't helpful. Not doing something with a numeric type and using an enum just muddies the waters).
use std::ops::AddAssign;
use std::ops::Neg;
The terminology you have used in your question is a little ambiguous - I am not sure what you mean by "applying" a trait. There are three main ways in which a trait appears:
A trait can be defined (where you specify the methods it has and its associated types).
A trait can be implemented (where you implement the trait's methods on some type)
A trait can be used (where you bring it into scope and call its methods).
There are some restrictions on where you can implement a trait. These restrictions are referred to as the "orphan rule", which you will find described on this page.
The rule states that you cannot implement a trait on a type unless either the trait is defined in your crate, and/or the type you are implementing it on is defined in your crate.
For example, you are free to define your own traits and then implement them on foreign types (such as those in the std library or other crates you are importing). You are also free to implement foreign traits on your own types. Both are common patterns.
There is a workaround for the orphan rule: the "newtype pattern". You can find info about that in this question.
So, to answer your specific questions:
Does rust necessitate Traits that provide these capability to be defined back in library code (money_typesafe below) referenced by the cargo.toml dependency (of main.rs) that gets used in the binary.
No. Traits can be defined in the binary crate.
Does rust allow Traits to be applied implemented in the using binary.
Yes, as long as either the trait, or the type it is implemented for, or both are defined in the binary crate.
allow both 1) & 2)?
Yes, traits can be both defined and implemented externally or inside the binary crate, subject to the orphan rule.
If 2, is permitted, does the syntax change on defining the Trait in any way?
No it does not. One thing to note though is that a trait has to be in scope for you to call its methods, so it is necessary to use traits that are not defined in the current scope.
Voldemort – he who must not be named – types are types whose names are impossible to write down in the source code. In Rust, closures have such types, because the compiler generates a new internal type for each closure. The only way to accept a closure as function argument is to accept a generic type (usually called F) which is bounded to be an Fn() (or similar) trait.
References in Rust always contain a lifetime parameter, even if this lifetime can usually be omitted. Lifetimes can't be named explicitly, because they represent some complex compiler-internal scope of some kind. The only way to interact with lifetimes is to use a generic parameter (usually called 'a) which stands for any lifetime (maybe bounded by another lifetime). Of course, there is 'static which can be named, but this is a special case and doesn't conflict with my arguing.
So: are Rust references Voldemort types? Or do I misunderstand the term “Voldemort type” or Rust references?
As someone without any particularly strong knowledge in the area:
I think the answer is probably: technically yes, but it's overly reductive. A bit like saying "all types are arrays of integers"; I mean, yes, but you're losing some useful semantic discrimination by doing that.
Voldemort types are usually to hide the implementation type from the user, either because it's only supposed to be a temporary, or you're not supposed to use anything but the interface described by the function. References are technically unnameable in their entirety, but it's not like it ever actually restricts you. I mean, even if you could name the specific lifetime, I don't think you could do anything meaningful with it (except possibly for slightly stricter lifetime checking within a function).
Arguably no. Are the types of references and pointers in all languages considered Voldemort types? They hide something, but no.
We envision lifetimes as being regions of code outside the called function. Also, they're created roughly like that in rustc. Yet, I'd argue function signatures are the type definition of the lifetimes we actually see. And rustc is merely satisfying them. There is nothing more to the named lifetimes than what you see in the function definition.
In C++, you have the ability to pass integrals inside templates
std::array<int, 3> arr; //fixed size array of 3
I know that Rust has built in support for this, but what if I wanted to create something like linear algebra vector library?
struct Vec<T, size: usize> {
data: [T; size],
}
type Vec3f = Vec<f32, 3>;
type Vec4f = Vec<f32, 4>;
This is currently what I do in D. I have heard that Rust now has Associated Constants.
I haven't used Rust in a long time but this doesn't seem to address this problem at all or have I missed something?
As far as I can see, associated constants are only available in traits and that would mean I would still have to create N vector types by hand.
No, associated constants don't help and aren't intended to. Associated anything are outputs while use cases such as the one in the question want inputs. One could in principle construct something out of type parameters and a trait with associated constants (at least, as soon as you can use associated constants of type parameters — sadly that doesn't work yet). But that has terrible ergonomics, not much better than existing hacks like typenum.
Integer type parameters are highly desired since, as you noticed, they enable numerous things that aren't really feasible in current Rust. People talk about this and plan for it but it's not there yet.
Integer type parameters are not supported as of now, however there's an RFC for that IIRC, and a long-standing discussion.
You could use typenum crate in the meanwhile.