How Type Deduction Works?

Why rust could deduce the type of adjusted_fee?

And I mock a minimal version, but it can not compile.

fn mock1() {
	let len_fee: u128 = 50;
	let weight_fee: u128 = 50;
	let adjustable_fee = len_fee.saturating_add(weight_fee);
	println!("adjustable_fee: {:?}", adjustable_fee);

	let targeted_fee_adjustment: Multiplier = Multiplier::saturating_from_integer(-1);
	println!("targeted_fee_adjustment: {:?}", targeted_fee_adjustment);

	let adjusted_fee: u128 =
		targeted_fee_adjustment.saturating_mul_acc_int(adjustable_fee.saturated_into());
	println!("adjusted_fee: u128: {}", adjusted_fee);

	let adjusted_fee: i128 =
		targeted_fee_adjustment.saturating_mul_acc_int(adjustable_fee.saturated_into());
	println!("adjusted_fee: i128: {}", adjusted_fee);

	// FIXME: type annotations needed
	// 	adjusted_fee -> ?
	// 	adjusted_fee.saturated_into() -> u128
	//
	// ```rust
	// let adjusted_fee =
	// 	targeted_fee_adjustment.saturating_mul_acc_int(adjustable_fee.saturated_into());
	// let _infer: u128 = 1;
	// let _infer: u128 = _infer.saturating_add(adjusted_fee.saturated_into());
	// ```
}

fn mock2<T>(len_fee: T, weight_fee: T, base_fee: T) -> T
where
	T: sp_runtime::traits::AtLeast32Bit + sp_arithmetic::fixed::FixedPointOperand,
{
	let adjustable_fee = len_fee.saturating_add(weight_fee);
	let targeted_fee_adjustment: Multiplier = Multiplier::saturating_from_integer(-1);
	let adjusted_fee =
		targeted_fee_adjustment.saturating_mul_acc_int(adjustable_fee.saturated_into());

	base_fee.saturating_add(adjusted_fee)
	// FIXME: type annotations needed
	// base_fee.saturating_add(adjusted_fee.saturated_into())
}

The actual algorithm is much more efficient than this, but conceptually the compiler does this:

  1. Assign every intermediate value a unique type name: A, B, C, ...
  2. For each of these, keep a list of all the allowable types:
    • If the user specifed the type explicitly, that’s the only type in the list.
    • Otherwise, the initial list contains all the types known to the compiler.
  3. Look at a subexpression, like _:A = _:B.into(). Cross off all of the possibilites for A that don’t match any possible Bs, and vice versa.
  4. Repeat step 3 for various subexpressions, until you can’t cross anything else off of a list.
  5. At this point, for compilation to continue, every list needs to contain exactly one item.
    • If a list is empty, the programmer has asked for something impossible, like storing a function result into a variable with an incompatible type.
    • If a list has more than one allowable type in it, there’s more than one program that matches the code— The compiler doesn’t know which one was intended.

Both of your examples fall into this last case: there are lots of different types that can be converted into a u128, and the compiler doesn’t have enough information to know which of those types you want adjusted_fee to be.

2 Likes

But I think the code in that link is also lack of infomation to deduce.

fn saturating_mul_acc_int<N>(self, n: N) -> N;

let adjusted_fee = targeted_fee_adjustment.saturating_mul_acc_int(adjustable_fee.saturated_into());
base_fee.saturating_add(adjusted_fee.saturated_into());

saturating_mul_acc_int accept type N then return type N.

We can easily know the type of adjustable_fee, adjusted_fee.saturated_into() are u128.

But what's the type of adjustable_fee.saturated_into() or adjusted_fee?

The inference chain in that linked code is pretty long:

  • adjustable_fee is the same type as len_fee and unadjusted_weight_fee:
let adjustable_fee = len_fee.saturating_add(unadjusted_weight_fee);
  • len_fee is the same type as per_byte and len:
let len_fee = per_byte.saturating_mul(len);
  • len is of type BalanceOf<T>, whatever that is:
let len = <BalanceOf<T>>::from(len);

So, adjustable_fee.saturated_into() and adjusted_fee should be...?

Right; I see what you mean now. I suspect it’s something to do with the UniqueSaturatedInto<u128> bound in the FixedPointOperand trait, but I’m not sure.

There must be only one solution for the intermediate value here, which seems unusual:

let _:BalanceOf<T> = <BalanceOf<T>>::saturated_into().saturated_into()

After some digging, it looks like the bounds for Balance<T> are different than the ones you use for mock2():

AtLeast32Bit + FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default

mock2 is a minimal version.

I think they are the same. Only the AtLeast32Bit and FixedPointOperand relate to arithmetic.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.