After some discussion, I'm now a little bit confused about the relation between auto-dereferencing and deref coercion.
It seems that the term "auto-dereferencing" applies only when the target to dereference is a method receiver,
whereas it seems that the term "deref coercion" applies to function arguments and all contexts it needs to.
I thought that a dereference does not always involve deref coercion, but I'm not sure: does dereferencing always use some Deref::deref trait implementation?
If so, is the implementor of T: Deref<Target = U> where T: &U built into the compiler?
Finally, it sounds natural to use the term "autoderef" in all the cases where the compiler implicitly transforms &&&&x to &x:
pub fn foo(_v: &str) -> bool {
false
}
let x="hello world";
foo(&&&&x);
Is this the general consensus of the community?
The parallels between the two cases are rather superficial.
In a method call expression, the compiler first needs to determine which method to call. This decision is based on the type of the receiver. The compiler builds a list of candidate receiver types, which include all types obtained by repeatedly derefencing the receiver, but also &T and &mut T for all types T encountered. This is the reason why you can call a method receiving &mut self directly as x.foo() instead of having to write (&mut x).foo(). For each type in the candidate list, the compiler then looks up inherent methods and methods on visible traits. See the language reference for further details.
A deref coercion is rather different. It only occurs at a coercion site where the compiler exactly knows what type to expect. If the actual type encountered is different from the expected type, the compiler can use any coercion, including a deref coercion, to convert the actual type to the expected type. The list of possible coercions includes unsized coercions, pointer weakening and deref coercions. See the the chapter on coercions in the Nomicon for further details.
So these are really two quite different mechanisms – one for finding the right method, and one for converting types when it is already known what type exactly to expect. The first mechanism also automatically references the receiver, which can never happen in a coercion.
I thought that a dereference does not always involve deref coercion, but I'm not sure: does dereferencing always use some Deref::deref trait implementation?
Not every dereferencing is a deref coercion. If you write *x, you explicitly dereference x. A deref coercion in contrast is performed implicitly by the compiler, and only in places where the compiler knows the expected type.
The semantics of dereferencing depend on whether the type of x is a pointer type, i.e. a reference or a raw pointer, or not. For pointer types, *x denotes the object x points to, while for other types *x is equivalent to *Deref::deref(&x) (or the mutable anlogue of this).
If so, is the implementor of T: Deref<Target = U> where T: &U built into the compiler?
I'm not quite sure what your syntax is supposed to mean – it's certainly not valid Rust syntax – but I guess you are asking whether derefencing an instance of &T to T is built into the compiler. As mentioned above, dereferencing of pointer types, including references, is built into the compiler, but there is also a blanket implementation of Deref for &T in the standard library. This blanket implementation is useful for generic code – the trait bound T: Deref<Target = U> otherwise wouldn't allow for T = &U.
Related
I am working on Make a Lisp to learn Rust.
As part of the evaluator step I need to create an associative structure mapping strings (or whatever) to functions. In languages I'm more familiar with (Ruby, Clojure) I would simply define anonymous functions in a hashmap, eg
{ :+ (fn [a b] (+ a b))
:- (fn [a b] (- a b)) } ; etc
In Rust, this isn't possible because of the type error expected closure, found a different closure.
let repl_env = HashMap::new();
repl_env.insert("+", |a, b| a + b);
repl_env.insert("-", |a, b| a - b); // expected closure, found a different closure
I guess what's happening here is
the compiler is inferring the types in the HashMap as <String, WhateverTypeTheFirstClosureIs>
the second closure doesn't have the same time because as the compiler error goes on to say, no two closures, even if identical, have the same type?
I can add types to the arguments and thus make the return type inferrable, but this doesn't help — I assume I need to somehow annotate the types of the closures themselves? I can't find a way to do this in the docs. The error suggests Boxing, but wrapping the closures in Box::new doesn't help. (I've never Boxed before).
I'm also wondering whether Rust closures in a HashMap are the right thing to use here. Should I maybe use an enum of some sort? (I'll want to dynamically add things later when I'm adding user-defined functions to my lisp, so I assume not).
You don't actually need closures here since you're not closing over anything, you're just using anonymous static functions.
That means you can just cast your anonymous functions to function pointers and it'll work fine e.g.
let mut repl_env: HashMap<_, fn(i32, i32) -> i32> = HashMap::new();
repl_env.insert("+", |a, b| a + b);
repl_env.insert("-", |a, b| a - b);
If you did need closures, then you'd have to use some sort of dynamic dispatch (as each closure is an entirely different type), generally something along the lines of `Box e.g.
let mut repl_env: HashMap<_, Box<dyn Fn(i32, i32) -> i32>> = HashMap::new();
repl_env.insert("+", Box::new(|a, b| a + b));
repl_env.insert("-", Box::new(|a, b| a - b));
That latter version is what the error message talks about: boxing the closure [and] (in this case) making it a trait object.
Should I maybe use an enum of some sort?
Using an enum might be a good idea as eventually you'll have functions with different signatures: here you're limited to the signature of (i32, i32) -> i32. Though you can alternatively update the universal signature to e.g. (Values) -> Value and internally perform dispatch / typechecking / ...
I'll want to dynamically add things later when I'm adding user-defined functions to my lisp, so I assume not.
If you want to keep the distinction between "native" and "user" functions, you can always have a variant which stores the result of evaluating a user function in your enum. You'll need a way to distinguish them anyway since at the end of the day the evaluator won't call them the same way.
Though I would think the usual method is to make all visible functions uniformly "userland", and implement those in terms of builtins (which may be distinguished syntactically, or special forms, or even pattern-matched by the compiler, and may or may not be accessible to userland).
I don't know how Lisps usually do it at the root, but e.g. you could have the interpreter evaluate the terms of
(+ a b)
and check that + is the known / standard function and a and b are known compatible types, then do the operation internally without actually calling anything.
The fun bit is that you can provide a properly defined + function as
(define (+ a b)
(+ a b))
which looks nonsensical at first glance since it should recurse on itself, but the inner node can be pattern-matched by the interpreter and so this serves as a hook for higher-order functions.
That is how many Smalltalks handled this, for instance. Non-literal sends would land into the trampoline method, which would then bounce into the built-in for the operation, invisibly.
Given a Rust program, which compiles correctly, can I get the compiler to tell me what the elided lifetimes were inferred to be?
The cases where the compiler (currently1) can allow elided lifetimes are actually so simple that there isn't much the compiler could tell you about what it inferred:
Given a function, all elided lifetimes have the same value.
The compiler doesn't accept elided lifetimes in cases where it would have a choice to make. The exception is in methods, but tying all lifetimes to self is nearly always what is intended, so it makes sense for it to make this assumption.
[1] If a future version of Rust performed more sophisticated inference on elided lifetimes, then this question might have a far less trivial answer. For example the compiler could analyse the entire codebase to deduce a coherent set of lifetimes for all functions (or impls or structs if elision was permitted there too).
After some discussion, I'm now a little bit confused about the relation between auto-dereferencing and deref coercion.
It seems that the term "auto-dereferencing" applies only when the target to dereference is a method receiver,
whereas it seems that the term "deref coercion" applies to function arguments and all contexts it needs to.
I thought that a dereference does not always involve deref coercion, but I'm not sure: does dereferencing always use some Deref::deref trait implementation?
If so, is the implementor of T: Deref<Target = U> where T: &U built into the compiler?
Finally, it sounds natural to use the term "autoderef" in all the cases where the compiler implicitly transforms &&&&x to &x:
pub fn foo(_v: &str) -> bool {
false
}
let x="hello world";
foo(&&&&x);
Is this the general consensus of the community?
The parallels between the two cases are rather superficial.
In a method call expression, the compiler first needs to determine which method to call. This decision is based on the type of the receiver. The compiler builds a list of candidate receiver types, which include all types obtained by repeatedly derefencing the receiver, but also &T and &mut T for all types T encountered. This is the reason why you can call a method receiving &mut self directly as x.foo() instead of having to write (&mut x).foo(). For each type in the candidate list, the compiler then looks up inherent methods and methods on visible traits. See the language reference for further details.
A deref coercion is rather different. It only occurs at a coercion site where the compiler exactly knows what type to expect. If the actual type encountered is different from the expected type, the compiler can use any coercion, including a deref coercion, to convert the actual type to the expected type. The list of possible coercions includes unsized coercions, pointer weakening and deref coercions. See the the chapter on coercions in the Nomicon for further details.
So these are really two quite different mechanisms – one for finding the right method, and one for converting types when it is already known what type exactly to expect. The first mechanism also automatically references the receiver, which can never happen in a coercion.
I thought that a dereference does not always involve deref coercion, but I'm not sure: does dereferencing always use some Deref::deref trait implementation?
Not every dereferencing is a deref coercion. If you write *x, you explicitly dereference x. A deref coercion in contrast is performed implicitly by the compiler, and only in places where the compiler knows the expected type.
The semantics of dereferencing depend on whether the type of x is a pointer type, i.e. a reference or a raw pointer, or not. For pointer types, *x denotes the object x points to, while for other types *x is equivalent to *Deref::deref(&x) (or the mutable anlogue of this).
If so, is the implementor of T: Deref<Target = U> where T: &U built into the compiler?
I'm not quite sure what your syntax is supposed to mean – it's certainly not valid Rust syntax – but I guess you are asking whether derefencing an instance of &T to T is built into the compiler. As mentioned above, dereferencing of pointer types, including references, is built into the compiler, but there is also a blanket implementation of Deref for &T in the standard library. This blanket implementation is useful for generic code – the trait bound T: Deref<Target = U> otherwise wouldn't allow for T = &U.
Given the type std::cell::Ref, it seems to be basically a pointer. Yet it doesn't have Hash, PartialEq or Eq trait implementations. Is there any fundamental reasons these traits haven't been added?
The Ref type provides a Deref trait for doing comparing, so you could just dereference it to check for equality. Similar types like MutexGuard, RwLockReadGuard and RwLockWeiteGuard also implement Deref but not any of Eq, Hash etc.
There have been some discussions on implementing these traits on these types but none of the approach was feasible. So they just never got implemented.
After some discussion, I'm now a little bit confused about the relation between auto-dereferencing and deref coercion.
It seems that the term "auto-dereferencing" applies only when the target to dereference is a method receiver,
whereas it seems that the term "deref coercion" applies to function arguments and all contexts it needs to.
I thought that a dereference does not always involve deref coercion, but I'm not sure: does dereferencing always use some Deref::deref trait implementation?
If so, is the implementor of T: Deref<Target = U> where T: &U built into the compiler?
Finally, it sounds natural to use the term "autoderef" in all the cases where the compiler implicitly transforms &&&&x to &x:
pub fn foo(_v: &str) -> bool {
false
}
let x="hello world";
foo(&&&&x);
Is this the general consensus of the community?
The parallels between the two cases are rather superficial.
In a method call expression, the compiler first needs to determine which method to call. This decision is based on the type of the receiver. The compiler builds a list of candidate receiver types, which include all types obtained by repeatedly derefencing the receiver, but also &T and &mut T for all types T encountered. This is the reason why you can call a method receiving &mut self directly as x.foo() instead of having to write (&mut x).foo(). For each type in the candidate list, the compiler then looks up inherent methods and methods on visible traits. See the language reference for further details.
A deref coercion is rather different. It only occurs at a coercion site where the compiler exactly knows what type to expect. If the actual type encountered is different from the expected type, the compiler can use any coercion, including a deref coercion, to convert the actual type to the expected type. The list of possible coercions includes unsized coercions, pointer weakening and deref coercions. See the the chapter on coercions in the Nomicon for further details.
So these are really two quite different mechanisms – one for finding the right method, and one for converting types when it is already known what type exactly to expect. The first mechanism also automatically references the receiver, which can never happen in a coercion.
I thought that a dereference does not always involve deref coercion, but I'm not sure: does dereferencing always use some Deref::deref trait implementation?
Not every dereferencing is a deref coercion. If you write *x, you explicitly dereference x. A deref coercion in contrast is performed implicitly by the compiler, and only in places where the compiler knows the expected type.
The semantics of dereferencing depend on whether the type of x is a pointer type, i.e. a reference or a raw pointer, or not. For pointer types, *x denotes the object x points to, while for other types *x is equivalent to *Deref::deref(&x) (or the mutable anlogue of this).
If so, is the implementor of T: Deref<Target = U> where T: &U built into the compiler?
I'm not quite sure what your syntax is supposed to mean – it's certainly not valid Rust syntax – but I guess you are asking whether derefencing an instance of &T to T is built into the compiler. As mentioned above, dereferencing of pointer types, including references, is built into the compiler, but there is also a blanket implementation of Deref for &T in the standard library. This blanket implementation is useful for generic code – the trait bound T: Deref<Target = U> otherwise wouldn't allow for T = &U.