How do I disambiguate different/mutual type associations that point to the same type? - rust

I have a series of traits that are mutually associated, and contain further type associations that are meant to be enums. I've left the Id type associations repeated in each trait that needs it, to avoid deeply nested fully-qualified names like <<World as World>::Exit as Exit>::SpotId but this is leading to confusion now that I'm implementing behavior relying on these "top-level" traits:
trait Accessible {
type Context: Ctx;
fn can_access(&self, ctx: &Self::Context) -> bool;
}
trait Id: Copy + Clone + Debug + Eq + Hash + Ord + PartialOrd {}
trait Location: Accessible {
type LocId: Id;
type ExitId: Id;
}
trait Exit: Accessible {
type ExitId: Id;
type SpotId: Id;
type LocId: Id;
}
trait World {
type Context: Ctx;
type Location: Location;
type Exit: Exit;
type SpotId: Id;
fn get_neighbors(&self, spot_id: Self::SpotId) -> &[Self::SpotId];
}
trait Ctx: Clone + Eq {
type World: World<Context = Self, SpotId = Self::SpotId>;
type SpotId: Id;
}
fn expand<T>(world: &impl World<Context = T>, ctx: &T, start: <T as Ctx>::SpotId)
where T: Ctx,
{
for spot in world.get_neighbors(start) { }
}
(on the rust playground)
This produces an error on world.get_neighbors:
| expected `World::SpotId`, found `Ctx::SpotId`
= note: expected associated type `<impl World<Context = T> as World>::SpotId`
found associated type `<T as context::Ctx>::SpotId`
and vice versa on some other uses of a SpotId.
Is there a clean way to maintain the mutual associations and have a simple typename for (e.g.) SpotId so that it doesn't matter which trait's version of SpotId is named?
I've tried to add more annotations to make clear that they must be inferred to be the same type:
trait World {
type Context: Ctx<SpotId = Self::SpotId, World = Self>;
type Location: Location + Accessible<Context = Self::Context>;
type Exit: Exit<SpotId = Self::SpotId> + Accessible<Context = Self::Context>;
type SpotId: Id;
}
but get the same error.
I've looked at https://users.rust-lang.org/t/type-alias-for-readability-with-associated-traits/64044/2 which recommends using type aliases to manage fully-qualified names, but I've been unsure how to get started with that.
Update: it's possible to specify that one type fulfills both requirements via a type parameter:
fn expand<T, S>(world: &impl World<Context = T, SpotId = S>, ctx: &T, start: S)
where T: Ctx<SpotId = S>, S: Id,
{
for spot in world.get_neighbors(start) { }
}
but as development continues, my functions start looking more like
fn explore<T, S, L, E>(
world: &impl World<
Context = T,
SpotId = S,
Exit = impl Exit<ExitId = E, SpotId = S> + Accessible<Context = T>,
Location = impl Location<LocId = L> + Accessible<Context = T>,
>,
ctx: &T,
)
where
T: Ctx<SpotId = S, LocationId = L, ExitId = E> + Debug,
S: Id,
L: Id,
E: Id,
{ ... }
and every caller has to be modified whenever I add a use of another id type (there are a few more I omitted here for brevity). I should probably seek a solution at the trait level.

All you have to do is require that these associated types are the same type in the definition of fn expand, which you can do by introducing another generic parameter:
fn expand<T: Ctx<SpotId = S>, S>(
world: &impl World<Context = T, SpotId = S>,
ctx: &T,
start: S
) {
for spot in world.get_neighbors(start) { }
}
Adding the parameter S and constraining both Ctx and World to have SpotId = S tells the compiler that they must be the same type in order to allow this function to be invoked.
(Playground)

Here's what ended up working. Ignoring the circular dependency in Accessible, I reorganized types so that World only has one path to each Id type, and then used fully-qualified syntax in the trait functions. Then, I changed the generic types in the functions to use the types that implement the place traits rather than the Ids, since it's easier to write the trait bounds, including some odd cases like this struct I added that really only needs Ctx and SpotId but has to provide the bound that they're related.
trait World {
type Location: Location;
type Exit: Exit<
ExitId = <Self::Location as Location>::ExitId,
LocId = <Self::Location as Location>::LocId,
Context = <Self::Location as Accessible>::Context,
>;
fn get_neighbors(&self, spot_id: <Self::Exit as Exit>::SpotId) -> &[<Self::Exit as Exit>::SpotId];
}
trait Ctx {
type World: World;
}
struct SpotAccess<T, S>
where
T: Ctx,
S: Id,
<T::World as World>::Exit: Exit<SpotId = S>,
{ ... }
fn expand<W, T, E>(world: &W, ctx: &T, start: SpotAccess<T, E::SpotId>)
where
W: World<Exit = E>,
T: Ctx<World = W>,
E: Exit<Context = T>,
{ ... }
I'm not sure if the bound on World::Exit that its associated types are the same as World::Location's but is required but I'll leave it for now.

Related

What exactly is the requirement for "covering" a type & why does a single element tuple satisfy it?

Assuming the following code is present
use core::any::Any;
enum Value {
Any(Box<dyn Any>),
Other, // placeholder, this code is adapted from mine
}
This code raises a diagnostic that I can't quite understand
impl<T: Any> TryFrom<Value> for T {
type Error = &'static str;
fn try_from(val: Value) -> Result<Self, Self::Error> {
if let Value::Any(any) = val {
if let Ok(down) = any.downcast::<T>() {
Ok(*down)
} else {
Err("incorrect type")
}
} else { Err("not an any") }
}
}
fn main() {
let res: Result<usize, &'static str> = Value::Any(Box::new(1usize)).try_into();
dbg!(res);
}
error[E0210]: type parameter `T` must be covered by another type when it appears before the first local type (`Value`)
--> src/main.rs:9:6
|
9 | impl<T: Any> TryFrom<Value> for T {
| ^ type parameter `T` must be covered by another type when it appears before the first local type (`Value`)
|
= note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local, and no uncovered type parameters appear before that first local type
= note: in this case, 'before' refers to the following order: `impl<..> ForeignTrait<T1, ..., Tn> for T0`, where `T0` is the first and `Tn` is the last
I still don't quite understand what "must be covered by another type" means, nor "when it appears before the first local type".
However, if I modify the impl signature to target a single-element tuple containing T, the impl does not raise an error, and the code functions correctly:
impl<T: Any> TryFrom<Value> for (T,) {
type Error = &'static str;
fn try_from(val: Value) -> Result<Self, Self::Error> {
if let Value::Any(any) = val {
if let Ok(down) = any.downcast::<T>() {
Ok((*down,))
} else {
Err("incorrect type")
}
} else { Err("not an any") }
}
}
fn main() {
let res: Result<(usize,), &'static str> = Value::Any(Box::new(1usize)).try_into();
dbg!(res);
}
What purpose does the single-element tuple actually serve?
(Playground Link)
From RFC 2451:
Covered Type: A type which appears as a parameter to another type. For example, T is uncovered, but the T in Vec<T> is covered. This is only relevant for type parameters.
It is important to note that the type T does not equal the tuple type (T,). (T,) can be considered equivalent to a hypothetical generic newtype/tuple struct struct Tuple1<T>(T) defined in the standard library crate std. With this analogy, impl<T: Any> TryFrom<Value> for (T,) is equivalent to impl<T: Any> TryFrom<Value> for std::Tuple1<T>.
Note that the covering type (in this case the single element tuple type, or in our analogy Tuple1) need not be defined locally in the same crate. To put it simply, consider an impl<T> ForeignTrait<LocalType> for ForeignType<T>:
The covering type ForeignType has already been defined. So:
The only way ForeignTrait<LocalType> can be implemented for
ForeignType<T> outside of the current crate is through a generic
impl <S, T> ForeignTrait<S> for ForeignType<T> (where S covers
LocalType).
Because of these rules, an impl <S, T> ForeignTrait<S> for ForeignType<T> that covers ForeignTrait<LocalType> is only possible in the crate declaring ForeignType.
Hence it is impossible for a conflicting implementation of ForeignTrait<LocalType> to exist for ForeignType<T> outside of a) the local crate and b) the crate declaring ForeignType, and so the impl is allowed. The RFC discusses this in more detail.

How does `.into()` know which type to convert to if `From` is implemented for multiple types?

How does the the <From> trait know which type to convert to in different contexts if it's implemented for more than one type?
For example, i have three types that have some mutual conversions implemented. On Character i implement From for both Token and AminoAcid. Yet, when i call .into() in the hashmap i don't have to specify which type is required by the map's type definition. How is the trait aware of the context?
#[derive(Debug)]
struct Coordinate{
x:f64, y:f64, z:f64
}
#[derive(Debug)]
enum AminoAcid {
VAL, GLN ,ARG,...
}
#[derive(Debug)]
enum Token {
Coordinate(Coordinate),
AminoAcid(AminoAcid),
Character(Character)
}
impl From<Coordinate> for Token {
fn from(coord: Coordinate) -> Self {
Self::Coordinate(coord)
}
}
impl From<AminoAcid> for Token {
fn from(aa: AminoAcid) -> Self {
Self::AminoAcid(aa)
}
}
// ------------------------------------------------- <<<<
impl From<Character> for Token {
fn from(c: Character) -> Self {
Self::Character(c)
}
}
impl From<Character> for AminoAcid {
fn from(c: Character) -> Self {
Self::ALA
}
}
// ------------------------------------------------- <<<<
lazy_static! {
static ref HASHMAP: HashMap<&'static str, Token> = { // Here the value type is Token
let mut m = HashMap::new();
m.insert("ALA", Character::Second.into());
m
};
static ref HASHMAP: HashMap<&'static str, AminoAcid> = { // Here the value type is AminoAcid
let mut m = HashMap::new();
m.insert("ALA", Character::Second.into());
m
};
}
It's because Rust can deduce/infer types.
For example
fn f1(arg: u8) {
println!("u8 in f1: {}", arg)
}
fn f2(arg: i64) {
println!("i64 in f2: {}", arg)
}
fn main() {
let a = 12;
let b = 23;
f1(a);
f2(b);
// f1(b); // expected `u8`, found `i64`
// f2(a); // expected `i64`, found `u8`
}
a and b are declared the same way; at this point, the compiler just knows they are some kind of integer.
At the first call of f1(), since the expected argument is an u8, Rust deduces that a is actually an u8.
The same goes for b deduced as an i64 at the first usage of f2().
Of course, if after that we try to make the compiler deduce other types for these variables, it fails.
In your case, you declare your static bindings as hashmaps of a specific type: Token in one case, AminoAcid in the other.
Then, the brace used to initialise such a binding have to match this type; the type of the resulting expression (m) is deduced accordingly.
The way we initialise m expects the correct value type.
Consequently, the version of into() providing this type is chosen.
Because you defined the type of HASHMAP, the compiler infers which type is needed, since if it converted into the other type it would not compile. https://doc.rust-lang.org/rust-by-example/types/inference.html

Is it possible for Rust to iterate over all the types in a module?

I have trait like this to describe the structure of a type, if I know the type at compile-time, of course I can inspect all the associate constants, associate types, and static member functions of it. But the point is, there're hundreds(or even more countless) of types defined in a module, which all satisfy this TypeModule trait, but I only got a TypeID value from run-time, how can I identify which is the right type I want and inspect all its associate constants, associate types, and static member functions?
pub trait Value: std::fmt::Debug {
const SERIALIZED_SIZE_HINT: usize; //此NBT数据类型序列化的预估大小
fn deserialize_from(src: &[u8]) -> DynamicValue;//必须正确实现,返回的type_id必须正确,不允许失败,无论src为何都必须返回一个正确的DynamicValue
fn serialize_into(dynamic_value: &DynamicValue) -> Vec<u8>;//不允许失败,因为内存中的DynamicValue的数据一定处于正确的状态
}
///每个ID写一个类型,实现这个Trait,静态分发用
pub trait TypeModule {
const TYPE_ID: TypeID<'static>;
const TAG_LIST: &'static [Tag];
type BlockValue: Value;
type EntityValue: Value;
type ItemValue: Value;
fn get_type_info() -> TypeInfo {
let block = if !is_same_type::<Self::BlockValue, ()>() {Some(SerializeDeserializeFunctions{
deserialize_from: Self::BlockValue::deserialize_from,
serialize_into: Self::BlockValue::serialize_into,
serialize_size_hint: Self::BlockValue::SERIALIZED_SIZE_HINT,
})} else {None};
let entity = if !is_same_type::<Self::EntityValue, ()>() {Some(SerializeDeserializeFunctions{
deserialize_from: Self::EntityValue::deserialize_from,
serialize_into: Self::EntityValue::serialize_into,
serialize_size_hint: Self::EntityValue::SERIALIZED_SIZE_HINT,
})} else {None};
let item = if !is_same_type::<Self::ItemValue, ()>() {Some(SerializeDeserializeFunctions{
deserialize_from: Self::ItemValue::deserialize_from,
serialize_into: Self::ItemValue::serialize_into,
serialize_size_hint: Self::ItemValue::SERIALIZED_SIZE_HINT,
})} else {None};
TypeInfo {
type_id: Self::TYPE_ID,
tags: Self::TAG_LIST,
block_functions: block,
entity_functions: entity,
item_functions: item,
}
}
}
Is that possible to write code iterating all the types in a module, so I can compare their associate TYPE_ID with the run-time given value and get the correct TAG_LIST,SERIALIZED_SIZE_HINT and all the member function pointers?
The closest effort I made is adding an function to TypeModule to turn a type into a TypeInfo value:
///Helper Function
struct SameType<T1, T2> {}
impl<T1, T2> SameType<T1, T2> {
const VALUE :bool = false;
}
impl<T> SameType<T, T> {
const VALUE :bool = true;
}
fn is_same_type<T1, T2>() -> bool {SameType::<T1, T2>::VALUE}
#[derive(PartialEq,Eq,Copy,Clone)]
pub enum Tag {
}
pub struct SerializeDeserializeFunctions {
deserialize_from: fn(&[u8]) -> DynamicValue,
serialize_into: fn(&DynamicValue) -> Vec<u8>,
serialize_size_hint: usize,
}
///write an instance of this type for every id, for dynamic dispatching
pub struct TypeInfo {
type_id: TypeID<'static>,
tags: &'static [Tag],
block_functions: Option<SerializeDeserializeFunctions>,
entity_functions: Option<SerializeDeserializeFunctions>,
item_functions: Option<SerializeDeserializeFunctions>,
}
I can turn a pre-known type into TypeInfo but I don't know how check for hundreds of types of a module.

Impl trait with generic associated type in return position causes lifetime error

I need to store a fn(I) -> O (where I & O can be references) in a 'static struct. O needs to be a trait with an 'static generic associated type, that associated type is also stored in the struct. Neither I nor O itself get stored inside of the struct, so their lifetime shouldn't matter. But the compiler is still complaining about I not living long enough.
trait IntoState {
type State: 'static;
fn into_state(self) -> Self::State;
}
impl IntoState for &str {
type State = String;
fn into_state(self) -> Self::State {
self.to_string()
}
}
struct Container<F, S> {
func: F,
state: S,
}
impl<I, O> Container<fn(I) -> O, O::State>
where
O: IntoState,
{
fn new(input: I, func: fn(I) -> O) -> Self {
// I & O lives only in the next line of code. O gets converted into
// a `'static` (`String`), that is stored in `Container`.
let state = func(input).into_state();
Container { func, state }
}
}
fn map(i: &str) -> impl '_ + IntoState {
i
}
fn main() {
let _ = {
// create a temporary value
let s = "foo".to_string();
// the temporary actually only needs to live in `new`. It is
// never stored in `Container`.
Container::new(s.as_str(), map)
// ERR: ^ borrowed value does not live long enough
};
// ERR: `s` dropped here while still borrowed
}
playground
As far as I can tell, the compiler's error message is misleading, what it actually requires is an explicitly defined associated type:
fn map(i: &str) -> impl '_ + IntoState<State = String> {
i
}
The excellent answer given to the quesion: Why does the compiler not infer the concrete type of an associated type of an impl trait return value? provides enough information on why this is actually needed.
See also Rust issue #42940 - impl-trait return type is bounded by all input type parameters, even when unnecessary
You can use a generic type parameter instead of returning an impl in which case you don't have to specify the associated type:
fn map<T: IntoState>(i: T) -> T {
i
}
Apparently there is still some confusion about what exactly is going on here, so I'll try to destil my comments into a short answer.
The problem here is the prototype of the function map():
fn map(i: &str) -> impl '_ + IntoState
This specifies that the return type of map() is some type implementing IntoState, with an unspecified associated type State. The return type has a lifetime parameter with the lifetime of the argument i; let's call that lifetime 'a, and the full return type T<'a>. The associated type State of this return type now is <T<'a> as IntoState>::State, which is parametrized by 'a. The compiler is currently not able to eliminate this lifetime parameter from the assoicated type, in spite of the 'static declaration in the trait definition. By explicitly specifying the associated type as String, the compiler will simply use the explicitly specified type String instead of <T<'a> as IntoState>::State, so the lifetime parameter is gone, and we don't get an error anymore.
This compiler shortcoming is discussed in this Github issue.

Type annotations required - why are associated types treated differently?

In the following program (play), the FooBar trait provides the bar method, but the actual type of the object returned by bar seems to be hidden. If I use a type argument instead of an associated type, it works (play).
Why are associated types treated differently? Or am I doing it wrong?
use std::ops::DerefMut;
pub trait FooBar: Sized {
type Assoc: Sized + DerefMut<Target=Self>;
fn foo(&mut self) -> Option<Self::Assoc>;
fn bar(mut this: Self::Assoc) -> Result<Self::Assoc, Self::Assoc> {
unimplemented!()
}
}
#[derive(Debug)]
struct Test(u32);
impl FooBar for Test {
type Assoc = Box<Test>;
fn foo(&mut self) -> Option<Self::Assoc> {
unimplemented!()
}
}
fn main() {
let mut tt = Test(20);
let tt_foo: Box<Test> = tt.foo().unwrap(); // this is ok
let tt_bar: Box<Test> = FooBar::bar(Box::new(tt)).unwrap(); // but not this
assert_eq!(tt_bar.0, 20);
}
If your method is
fn bar(mut this: Self::Assoc) -> Result<Self::Assoc, Self::Assoc>
and you try to call it with
FooBar::bar(Box::new(tt))
how is Rust supposed to know what type Self is? Box::new(tt) is Self::Assoc right, but you can’t get Self from that, several types could have the same Assoc.
And that’s what rustc is complaining about:
type annotations required
You’d have to annotate what type Self is:
let tt_bar: Box<Test> = <Test as FooBar>::bar(Box::new(tt)).unwrap();
or equivalently:
let tt_bar: Box<Test> = Test::bar(Box::new(tt)).unwrap();
The problem is you are trying to access the associated type from the trait. You can only access it from a type that implements the trait, such as from Test:
let tt_bar: Box<Test> = Test::bar(Box::new(tt)).unwrap();
FooBar::Assoc is not a concrete type, so you cannot use it. When you implemented FooBar for Test, you gave Test::Assoc a concrete type, which is accessible:
type Assoc = Box<Test>;
In the code with the generic type, a new copy of FooBar::bar was created with a concrete type. Because you requested a Box<Test>, the new function's signature would be this:
fn bar(mut this: Box<Test>) -> Result<Box<Test>, Box<Test>>
Box<Test> is a concrete type, so it works.

Resources