If I understand your problem correctly (which I may not, I'm human), your goal is to check for whether a T satisfies a bound without the compiler performing monomorphization (like through impl<T: Foo> Bar for T).
You have multiple options. Either you only deal with dynamic dispatch (therefore implementing your trait on dyn Foo rather than a monomorphized T), or you deal with type reflection (experimental, see https://github.com/rust-lang/rust/issues/146922).
I don't see why you want to avoid static dispatch (i.e. monomorphization), as sure, it makes the compiler have to determine which types implement a certain trait, but this is usually fast and creates hot paths for implementors to take instead of going through a vtable.
#Trait bound without type inference
39 messages · Page 1 of 1 (latest)
I am using monomorphization to set variable type after those are declared, and then i use those variables in an operation that requires them to have certain types but here i dont want the variables to automatically get the appropriate types. This is entirely at compile time no static dispatch.
In other words: I define a, b, c with undefined types.
I "set" type for a and b. (using monomorphization)
And then i do an "operation" to get c which requires a and b to be typed in a certain way.
I would want that if the type for b isnt set then the "operation" don't "set" it through monomorphization and instead give a "couldnt resolve type for variable b" error
this is the code (after macro expansion)
warning: it is a mess and still wip
then if you deal with "undefined" types, or rather erasing the types differently at runtime, there's only dynamic dispatch that is gonna solve the issue
by undefined type i mean which has not been resolved by the type inference yet
this is all compile time shenanigans
I'd appreciate then if you could give an example that does not look like obfuscated code :p
Or you can try to explain what you want to achieve in the first place, why you need to have "unresolved types", and what you mean by "no type inference yet" as type inference only happens at compile time
i am making a proc macro that parse maths statements and build types and traits at compile time to represent operations you can apply on the statements such as isolating a variable etc
basically using types to represent relationships between variables such you can then plug any value into those and the solving code structure already exist at compile time sort of thing
why not have the traits and solving code structure already present in, say, a crate? What is the requirements for using a proc macro instead?
because the macro is doing all the simplification logic then figuring out if its linear or quadratic etc... to then produce isolated form for each variable (and other things later)
those are then encoded into their own types which implement traits from the actual crate
eg:
pub struct cIsolation<'a, a, c, x> {
a: &'a a,
x: &'a x,
_marker: std::marker::PhantomData<c>,
}
impl<'a, _T, a, c, x> ComputeIsolate<_T> for cIsolation<'a, a, c, x>
where
_T: std::ops::Add<Output = _T> + std::ops::Neg<Output = _T>,
c: Unknown<_T>,
a: Constant<_T>,
x: Constant<_T>,
{
fn compute(&self) -> _T {
(-(-(self.x.get_constant() + self.a.get_constant())))
}
}
this encodes how to compute c (using the isolated form)
the goal isnt to compute the properties through the type system, dealing with variables etc... but instead to produce types using macro that have those properties already implemented
anyways this is quite involded and mostly an experiment however getting back to my first message, i am trying to find a trick to get the type inference to work one way (checking if bound is correct) without the other (doing actual monomorphization?)
I assume that, for example, your generic c changes from implementing Unknown to Constant?
In this case, have you considered using the newtype pattern? like, instead of tagging them with traits, using tuple struct wrappers to easily change their """type""" and therefore what they implement or not
at first it implements nothing but when calling set_constant through monomorphization it get the "Constant" trait
however this also happen when i use ComputeIsolate (for variables where i dont use set_constant) which i would like not to be the case
i dont get what you mean by newtype pattern
pub struct Unknow<T>(T);
pub struct Constant<T>(T);
// you can then implement specific behavior for Unknow/Constant
// and can easily move T from one to the other
let c: _ // become Variable0<ConstantState<RealValue>> through set_constant (Constant<T> trait method)
let a: _ // become Variable1<ConstantState<RealValue>> through ComputeIsolate <--- this idealy should not happen
let x: _ // become Variable2<UnknownState<RealValue>> through ComputeIsolate
// SomeVariable<ConstantState<T>> implement Constant<T>
// SomeVariable<UnknownState<T>> implement Unknown<T>
this is the current state of things
basically i am looking for a way to obscure the relationship between ComputeIsolate and Constant such that the trait can only be used if the variable implement Constant but such that monomorphization to Variable1<UnknownState<RealValue>> doesnt happen (only) in this particular case
*fixed mistakes i made writing this
This is quite intricate and I'm trying to wrap my head around the issue.
You mean you have some wrappers like VariableN<UnknownState<const RealValue>> but monomorphization somehow considers UnknownState an implementor of Constant?
Forgive me for the amount of questions, it's hard to understand it all without an overview of the problem and the different moving parts that composes it. Do you have a published documentation (docs.rs) somewhere perchance?
no problem i totally understand that this is quite hard to wrap your head around thanks for still trying to help me
i can give you the code
let (c, x, a) = variables!(); // Each variable is a type VariableN<T>, T is unknowned to the compiler at the moment however VariableN<T> implement TypedVariable<T>
pub trait Constant<T> {
fn set_constant(&self, value: T);
fn get_constant(&self) -> T;
}
impl<T: TypedVariable<ConstantState<U>>, U: Copy> Constant<U> for T {
fn set_constant(&self, value: U) {
self.get_inner().value.set(value);
}
fn get_constant(&self) -> U {
self.get_inner().value.get()
}
}
c.set_constant(Real(42.)); // Here c becomes Variable0<ConstantState<RealValue>> through monomorphization by the Constant trait
impl<'a, _T, a, c, x> ComputeIsolate<_T> for xIsolation<'a, a, c, x>
where
_T: From<f64>
+ std::ops::Sub<Output = _T>
+ std::ops::Div<Output = _T>
+ std::ops::Neg<Output = _T>,
x: Unknown<_T>,
a: Constant<_T>,
c: Constant<_T>,
{
fn compute(&self) -> _T {
((-(self.c.get_constant() - self.a.get_constant())) / _T::from(-1f64))
}
}
stmt.solve_for(&x) // This calls compute from ComputeIsolate which need c and a to be Constant. "c" already implement Constant but not "a", as such type of "a" resolve into Variable1<ConstantState<RealValue>> which i dont want it to do
we dont really need to care about the Unknown trait here, my goal would be that each constant has to be defined (using set_constant) in order for ComputeIsolate to be usable and that the ComputeIsolate trait wouldnt define them (to defaults) through monomorphization
if you are willing to look into it further
How is it possible that we can call set_constant on c in this case? Because if c, at creation, is indeed just VariableN(T): TypedVariable<T>, then I don't get how this T can be inferred to satisfy ConstantState<T>, unless we have a declaration elsewhere that implements ConstantState<T> on every T that VariableN can contain / that TypedVariable<T> is implemented on
PS: in your impl ComputeIsolate for xIsolation, you probably should capitalize a, c, and x, so that we can more easily differentiate from the variables named that way, from the generic type associated to such variables, but that's just aesthetics, so feel free to ignore :)
theyr are not capitalized because the macro produce those from the input statement and if you use upper and lower case in the statement those would get mixed up in the generics
impl<T: TypedVariable<ConstantState<U>>, U: Copy> Constant<U> for T // The compiler want Constant<T> so it tries to make the type implement TypedVariable<ConstantState<T>>
impl<T> TypedVariable<T> for VariableN<T> // to have VariableN<X> implement TypedVariable<ConstantState<T>> it make it into VariableN<ConstantState<T>>
so id think if there is solution to my issue it would probably be in adding a "layer of indirection (type system wise)" between Constant<T> and the actual trait bound for ComputeIsolate to obscure the type inference to the compiler
but i don't see a trick using gats or tags, maybe with exotic types such as function pointers or idk
You say
The compiler want Constant<T> so it tries to make the type implement TypedVariable<ConstantState<T>>
But I'm not completely sure that is what actually happens (again, may be wrong on that).
From how I see the code, the compiler is tasked with resolving the implementation by asking "from the set of all used types within the codebase, which of them satisfy implementsTypedVariableof an implementor ofConstantStateof a type that is an implementor ofCopy", then creates the relevant monomorphized versions of the implementation.
This is where I get confused. If your variables are implementors of TypedVariable<U> where U does not implement ConstantState, then I don't see how it's possible that set_constant be called.
I'll check your source code, not sure I'll get the answer though, we'll see
warning: the code is messssy
impl<T> TypedVariable<T> for VariableN<T> // T could be anything? why do you say does not implement ConstantState?
impl<T: TypedVariable<ConstantState<U>>, U: Copy> Constant<U> for T // U here is refering to the generic on ConstantState which itself also could be anything
why do you say does not implement ConstantState?
If it does, then the issue lies more with why it is if it's not what you'd expect