So say I'm writing a wrapper type for the array.
struct Array<const L: usize, T> {
raw: [T;L]
}
And I have some function that mutates the length of the array wrapper, say the function is concatenation:
impl<const L: usize, T> Array<L, T> {
fn concat<const L2: usize>(self, other: Array<L, T>) -> Array<{L + L2}, T> {todo!();}
}
When I try to compile this code, the rust compiler gets extremely mad. Thinking it might have something to do with adding types corresponding to implementing multiple, traits, i tried multiplication instead of addition, which also didn't work.
I know that rust can evaluate some expressions at compile time, is this just a case where that isn't allowed, or am I missing something?
When I try to compile this code, the rust compiler gets extremely mad. […] I know that rust can evaluate some expressions at compile time, is this just a case where that isn't allowed, or am I missing something?
You say that the compiler gets mad at you, but have you considered listening at what it was telling you?
Plugging your code into the playground, the first error is a trivial showstopper of
error: type parameters must be declared prior to const parameters
--> src/lib.rs:1:30
|
1 | struct Array<const L: usize, T> {
| -----------------^- help: reorder the parameters: lifetimes, then types, then consts: `<T, const L: usize>
so it kind-of doesn't seem to because that's trivial to fix.
Now fixing that we get to the meat of the issue:
= help: const parameters may only be used as standalone arguments, i.e. `L`
= help: use `#![feature(const_generics)]` and `#![feature(const_evaluatable_checked)]` to allow generic const expressions
that seems to explain the entirety of the thing: what's currently enabled in Rust is the
Const generics MVP
MVP as in minimum viable products aka the smallest featureset which can be useful, and what that translates to is explained in the introductory blog post.
The first limitation is
Only integral types are permitted for const generics
which is fine here because you're using integral types, but the second limitation is
No complex generic expressions in const arguments
What is a complex generic expression? Anything outside of:
A standalone const parameter.
A literal (i.e. an integer, bool, or character).
A concrete constant expression (enclosed by {}), involving no generic parameters.
What you're trying to do does not work (outside of nightly with both const_generics and const_evaluatable_checked enabled) because you're writing constant expressions involving at least one generic parameter, which is not part of the MVP.
Related
In Rust, one often sees functions that take &str as a parameter.
fn foo(bar: &str) {
println!("{}", bar);
}
When calling functions like this, it is perfectly fine to pass in a String as an argument by referencing it.
let bar = String::from("bar");
foo(&bar);
Strictly speaking, the argument being passed is an &String and the function is expecting an &str, but Rust (as one would hope) just figures it out and everything works fine. However, this is not the case with match statements. If I try and use the same bar variable as before in a match statement, the naive usage will not compile:
match &bar {
"foo" => println!("foo"),
"bar" => println!("bar"),
_ => println!("Something else")
};
Rustc complains that it was expecting an &str but received an &String. The problem and solution are both very obvious: just borrow bar more explicitly with .as_str(). But this brings me to the real question: why is this the case?
If Rust can figure out that an &String trivially converts to an &str in the case of function arguments, why can't it do the same thing with match statements? Is this the result of a limitation of the type system, or is there hidden unsafety in fancier borrowing with match statements? Or is this simply a case of a quality of life improvement getting integrated into some places but not others? I'm sure someone knowledgeable about type systems has an answer, but there seems to be very little information about this little quirk of behavior on the internet.
The technical reason it doesn't work is because the match scrutinee is not a coercion site. Function arguments, as shown in your foo(&bar) example, are possible coercion sites; and it allows you to pass a &String as a &str because of Deref coercion.
A possible reason why its not a coercion site is that there's no clear type that it should be coerced to. In your example you'd like it to be &str since that matches the string literals, but what about:
match &string_to_inspect {
"special" => println!("this is a special string"),
other => println!("this is a string with capacity: {}", other.capacity()),
};
One would like the match to act like &str to match the literal, but because the match is on a &String one would expect other to be a &String as well. How to satisfy both? The next logical step would be for each pattern to coerce as required, which has been much desired... but it opens a whole can of worms since Deref is user-definable. See deref patterns from the Rust lang-team for more info.
https://doc.rust-lang.org/reference/type-coercions.html says:
Coercion sites
A coercion can only occur at certain coercion sites in a program; these are typically places where the desired type is explicit or can be derived by propagation from explicit types (without type inference). Possible coercion sites are:
[...]
Arguments for function calls
The value being coerced is the actual parameter, and it is coerced to the type of the formal parameter.
but not a match scrutinee.
Coercion types
Coercion is allowed between the following types:
[...]
&T or &mut T to &U if T implements Deref<Target = U>.
I am converting a variety of types to String when they are passed to a function. I'm not concerned about performance as much as ergonomics, so I want the conversion to be implicit. The original, less generic implementation of the function simply used &[impl Into<String>], but I think that it should be possible to pass a variety of types at once without manually converting each to a string.
The key is that ideally, all of the following cases should be valid calls to my function:
// String literals
perform_tasks(&["Hello", "world"]);
// Owned strings
perform_tasks(&[String::from("foo"), String::from("bar")]);
// Non-string types
perform_tasks(&[1,2,3]);
// A mix of any of them
perform_tasks(&["All", 3, String::from("types!")]);
Some various signatures I've attempted to use:
fn perform_tasks(items: &[impl Into<String>])
The original version fails twice; it can't handle numeric types without manual conversion, and it requires all of the arguments to be the same type.
fn perform_tasks(items: &[impl ToString])
This is slightly closer, but it still requires all of the arguments to be of one type.
fn perform_tasks(items: &[&dyn ToString])
Doing it this way is almost enough, but it won't compile unless I manually add a borrow on each argument.
And that's where we are. I suspect that either Borrow or AsRef will be involved in a solution, but I haven't found a way to get them to handle this situation. For convenience, here is a playground link to the final signature in use (without the needed references for it to compile), alongside the various tests.
The following way works for the first three cases if I understand your intention correctly.
pub fn perform_tasks<I, A>(values: I) -> Vec<String>
where
A: ToString,
I: IntoIterator<Item = A>,
{
values.into_iter().map(|s| s.to_string()).collect()
}
As the other comments pointed out, Rust does not support an array of mixed types. However, you can do one extra step to convert them into a &[&dyn fmt::Display] and then call the same function perform_tasks to get their strings.
let slice: &[&dyn std::fmt::Display] = &[&"All", &3, &String::from("types!")];
perform_tasks(slice);
Here is the playground.
If I understand your intention right, what you want is like this
fn main() {
let a = 1;
myfn(a);
}
fn myfn(i: &dyn SomeTrait) {
//do something
}
So it's like implicitly borrow an object as function argument. However, Rust won't let you to implicitly borrow some objects since borrowing is quite an important safety measure in rust and & can help other programmers quickly identified which is a reference and which is not. Thus Rust is designed to enforce the & to avoid confusion.
Is it possible to convert type T into generic type without use of into() ?
struct MyType<T>(T);
impl<T> From<T> for MyType<T> {
fn from(v: T) -> Self {
Self(v)
}
}
#[allow(unused_variables)]
fn main() {
let test1 = MyType(12);
//let test2: MyType<i32> = 12; // why is this not working
let test3: MyType<i32> = 12.into();
let test4 = MyType::from(12);
}
fn test_fnc_1() -> MyType<i32> {
12 // not working
12.into()
}
I want to do "test2", but this is not possible. I have to do "test3".
Another sample, I thought it is somehow possible, is in test_func_1.
Playground
The reason it is not possible is because the cost of converting a value into your time can be arbitrarily large. Implicit conversion would hide this cost, which is not desirable for system language.
As far as I know there are no plans to add implicit conversion into Rust.
Rust is fairly reserved about implicit conversions. In Type Coercions in the Rust Reference you can see most of them are dictated by the compiler, with Deref being the only user-defined conversion that may be done implicitly. An exception of course is the ? operator which does invoke a From conversion.
There is no implicit From conversion that is called when passing arguments, returning a value, or assigning to a variable. You will have to use .into() or T::from().
What you're looking for is implicit type conversion, which isn't possible in Rust. As you've seen, you have to explicitly construct types at some point (i.e. MyType(...) in your example), and that can be done either directly or with a function call.
There are some special cases where implicit coercion occurs, but these are generally only when dealing with references or traits, not concrete value types.
In this question, an issue arose that could be solved by changing an attempt at using a generic type parameter into an associated type. That prompted the question "Why is an associated type more appropriate here?", which made me want to know more.
The RFC that introduced associated types says:
This RFC clarifies trait matching by:
Treating all trait type parameters as input types, and
Providing associated types, which are output types.
The RFC uses a graph structure as a motivating example, and this is also used in the documentation, but I'll admit to not fully appreciating the benefits of the associated type version over the type-parameterized version. The primary thing is that the distance method doesn't need to care about the Edge type. This is nice but seems a bit shallow of a reason for having associated types at all.
I've found associated types to be pretty intuitive to use in practice, but I find myself struggling when deciding where and when I should use them in my own API.
When writing code, when should I choose an associated type over a generic type parameter, and when should I do the opposite?
This is now touched on in the second edition of The Rust Programming Language. However, let's dive in a bit in addition.
Let us start with a simpler example.
When is it appropriate to use a trait method?
There are multiple ways to provide late binding:
trait MyTrait {
fn hello_word(&self) -> String;
}
Or:
struct MyTrait<T> {
t: T,
hello_world: fn(&T) -> String,
}
impl<T> MyTrait<T> {
fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait<T>;
fn hello_world(&self) -> String {
(self.hello_world)(self.t)
}
}
Disregarding any implementation/performance strategy, both excerpts above allow the user to specify in a dynamic manner how hello_world should behave.
The one difference (semantically) is that the trait implementation guarantees that for a given type T implementing the trait, hello_world will always have the same behavior whereas the struct implementation allows having a different behavior on a per instance basis.
Whether using a method is appropriate or not depends on the usecase!
When is it appropriate to use an associated type?
Similarly to the trait methods above, an associated type is a form of late binding (though it occurs at compilation), allowing the user of the trait to specify for a given instance which type to substitute. It is not the only way (thus the question):
trait MyTrait {
type Return;
fn hello_world(&self) -> Self::Return;
}
Or:
trait MyTrait<Return> {
fn hello_world(&Self) -> Return;
}
Are equivalent to the late binding of methods above:
the first one enforces that for a given Self there is a single Return associated
the second one, instead, allows implementing MyTrait for Self for multiple Return
Which form is more appropriate depends on whether it makes sense to enforce unicity or not. For example:
Deref uses an associated type because without unicity the compiler would go mad during inference
Add uses an associated type because its author thought that given the two arguments there would be a logical return type
As you can see, while Deref is an obvious usecase (technical constraint), the case of Add is less clear cut: maybe it would make sense for i32 + i32 to yield either i32 or Complex<i32> depending on the context? Nonetheless, the author exercised their judgment and decided that overloading the return type for additions was unnecessary.
My personal stance is that there is no right answer. Still, beyond the unicity argument, I would mention that associated types make using the trait easier as they decrease the number of parameters that have to be specified, so in case the benefits of the flexibility of using a regular trait parameter are not obvious, I suggest starting with an associated type.
Associated types are a grouping mechanism, so they should be used when it makes sense to group types together.
The Graph trait introduced in the documentation is an example of this. You want a Graph to be generic, but once you have a specific kind of Graph, you don't want the Node or Edge types to vary anymore. A particular Graph isn't going to want to vary those types within a single implementation, and in fact, wants them to always be the same. They're grouped together, or one might even say associated.
Associated types can be used to tell the compiler "these two types between these two implementations are the same". Here's a double dispatch example that compiles, and is almost similar to how the standard library relates iterator to sum types:
trait MySum {
type Item;
fn sum<I>(iter: I)
where
I: MyIter<Item = Self::Item>;
}
trait MyIter {
type Item;
fn next(&self) {}
fn sum<S>(self)
where
S: MySum<Item = Self::Item>;
}
struct MyU32;
impl MySum for MyU32 {
type Item = MyU32;
fn sum<I>(iter: I)
where
I: MyIter<Item = Self::Item>,
{
iter.next()
}
}
struct MyVec;
impl MyIter for MyVec {
type Item = MyU32;
fn sum<S>(self)
where
S: MySum<Item = Self::Item>,
{
S::sum::<Self>(self)
}
}
fn main() {}
Also, https://blog.thomasheartman.com/posts/on-generics-and-associated-types has some good information on this as well:
In short, use generics when you want to type A to be able to implement a trait any number of times for different type parameters, such as in the case of the From trait.
Use associated types if it makes sense for a type to only implement the trait once, such as with Iterator and Deref.
When declaring a variable of type vector or a hash map in Rust, we do:
let v: Vec<int>
let m: HashMap<int, int>
To instantiate, we need to call new(). However, we do so thusly:
Vec::<int>::new()
^^
HashMap::<int, int>::new()
^^
Note the sudden appearance of ::. Coming from C++, these are odd. Why do these occur? Does having a leading :: make IDENTIFIER :: < IDENTFIER … easier to parse than IDENTIFIER < IDENTIFIER, which might be construed as a less-than operation? (And thus, this is simply a thing to make the language easier to parse? But if so, why not also do it during type specifications, so as to have the two mirror each other?)
(As Shepmaster notes, often Vec::new() is enough; the type can often be inferred.)
When parsing an expression, it would be ambiguous whether a < was the start of a type parameter list or a less-than operator. Rust always assumes the latter and requires ::< for type parameter lists.
When parsing a type, it's always unambiguously a type parameter list, so ::< is never necessary.
In C++, this ambiguity is kept in the parser, which makes parsing C++ much more difficult than parsing Rust. See here for an explanation why this matters.
Anyway, most of the time in Rust, the types can be inferred and you can just write Vec::new(). Since ::< is usually not needed and is fairly ugly, it makes sense to keep only < in types, rather than making the two syntaxes match up.
The two different syntaxes don't even specify the same type parameters necessarily.
In this example:
let mut map: HashMap<K, V>;
K and V fill the type parameters of the struct HashMap declaration, the type itself.
In this expression:
HashMap::<K, V>::new()
K and V fill the type parameters of the impl block where the method new is defined! The impl block need not have the same, as many, or the same default, type parameters as the type itself.
In this particular case, the struct has the parameters HashMap<K, V, S = RandomState> (3 parameters, 1 defaulted). And the impl block containing ::new() has parameters impl<K, V> (2 parameters, not implemented for arbitrary states).