Typescript two generics that are compatible in function - node.js

So I have a function that accepts two callbacks with single arguments, like this:
function f<T1, T2>(a: (input: T1) => number, b: (input: T2) => number) {}
What is want is that T1 and T2 should have such a relation that T1 is assignable to T2. That is, I want it to accept such call:
f((input: {a: number, b: number}) => 2, (input: {a: number}) => 4);
as {a: number, b: number} is assignable to {a: number}. But such call should not compile:
f((input: {}) => 2, (input: {a: number}) => 4);
What I tried doing is rewriting my function declaration in the following way:
function f<T1 extends T2, T2>(a: (input: T1) => number, b: (input: T2) => number) {}
But somehow the second example still gets compiled. Somehow, Typescripts assigns type {a: number} to T1 as well.....

The main reason you're running into trouble is that function types are contravariant in the types of their parameters (see Difference between Variance, Covaraince, Contravariance and Bivariance in TypeScript for more details). For example:
const contravariantFuncParam: (a: { x: string, y: number }) => void =
(a: { x: string }) => { }; // okay
// {x: string} is assignable to {x: string, y: number}
const notCovariantFuncParam: (a: { x: string }) => void =
(a: { x: string, y: number }) => { }; // error!
// {x: string, y: number} is not assignable to {x: string}
So for the following definition,
function f<T1 extends T2, T2>(a: (input: T1) => number, b: (input: T2) => number) { }
the compiler happily accepts this:
f((input: {}) => 2, (input: { a: number }) => 4);
Roughly, the type T2 is inferred from the input to the b callback as {a: number}. Then the compiler knows that T1 is constrained to {a: number}. Since function types are contravariant in their parameters, the compiler sees that (input: {}) => number is assignable to (input: {a: number}) => number, and thus the a input is accepted. And T1 falls back to the constraint {a: number}.
You'd prefer T2 and T1 to be inferred from b and a directly, and then only later enforce T1 extends T2. But by having T1 constrained to T2 this doesn't happen.
One way to do this is to move the T1 extends T2 clause out of the constraint position, and instead use a conditional type for the input to a:
function f<T1, T2>(
a: (input: T1 extends T2 ? T1 : unknown) => number,
b: (input: T2) => number
) { }
f((input: { a: number, b: number }) => 2, (input: { a: number }) => 4); // okay
f((input: {}) => 2, (input: { a: number }) => 4); // error
This works because when faced with inferring from a conditional type like A extends B ? C : D, the compiler will plug in its inferred type for A and then evaluate it. So T1 extends T2 ? T1 : unknown will cause the compiler to infer T1 as the input type to the passed-in a callback. If T1 extends T2 is true, then this is a no-op and the type for input evaluates to T1. Otherwise, the type for input will evaluate to unknown... which, by contravariance of function parameters, will almost certainly fail to accept whatever was passed in.
At this point I see that the two type parameters aren't really necessary, at least for this example. If you could infer T1 from a and not from b, then later go back and just check that T1 is usable as b's callback parameter type (which you used to call T2). If so, then (input: T2)=> number is assignable to (input: T) => number, and thus (by contravariance of function arguments) that T1 extends T2. So let's get rid of T2and just useTinstead ofT1`.
The tricky part here is how to tell the compiler that you want T to be inferred from a and not inferred from b. For b's input type, you want a NoInfer<T> where NoInfer means "just check this, don't use it to infer". If we had such a thing, you could write f() like this:
function f<T>(a: (input: T) => number, b: (input: NoInfer<T>) => number) { }
There is a feature request at microsoft/TypeScript#14829 for a way to mark non-inferential type parameter usage. No official solutions exist, but there are a few suggestions that work for some use cases, like type NoInfer<T> = T & {} from here. I tend to use this one:
type NoInfer<T> = [T][T extends any ? 0 : never];
Using this definition leads to your desired behavior:
f((input: { a: number, b: number }) => 2, (input: { a: number }) => 4); // okay
f((input: {}) => 2, (input: { a: number }) => 4); // error
Both of the above solutions are using tricks/workarounds to try to alter the compiler's type inference behavior. And these tricks are not guaranteed to work in all possible circumstances. Conditional type inference and NoInfer implementations have their pitfalls. So for either solution I strongly recommend extensive testing against likely use cases.
Playground link to code

Related

Why is type not equal to recomposed-decomposed itself?

I have deconstructed params:T into a + ...restParam, why can't I construct it back into T?
Essentially, why isn't this true:
T === Omit<T, "a"> & { a: number }
where T extends { a: number}?
function foo<T extends { a: number }>(param: T) {
const { a, ...restParam } = param;
const t1: T = {a, ...restParam} ; // <-- getting the following error here
}
TS2322: Type '{ a: number; } & Omit<T, "a">' is not assignable to type 'T'.   '{ a: number; } & Omit<T, "a">' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ a: number; }'.
I am guessing that T in my case could be more than just a record, e.g. it could be a string that also has a: number property. In which case destructuring param will loose that type information.
Is there a way to declare my foo function to only allow T that's a record (hashtable)?

Macro to match specific identifiers for inline lookups

Okay, writing my absolute first project in Rust. So, I have something like the following sort of setup:
use phf;
use std::str;
struct Values {
a: Option<char>,
b: Option<char>,
c: Option<char>
}
static MAPPING: phf::Map<&'static str, Values> = phf::phf_map! {
"some_key" => Values {
a: Some('a'),
b: Some('b'),
c: None
},
"some_other_key" => Values {
a: Some('1'),
b: None,
c: None
},
"some_third_key" => Values {
a: None,
b: Some('x'),
c: Some('y')
}
}
static NULL_VALUES: Values = Values {
a: None,
b: None,
c: None
}
// Should return a &str for any given key/val
#[macro_export]
macro_rules! get_value {
($key: ident, $val: ident) => {{
use crate::values::MAPPING;
use std::str;
let result = MAPPING.get("$key");
if let Some(r) = result {
if let Some(c) = r.$val {
if let Ok(s) = str::from_utf8(%[c as u8]) { s } else { "" }
} else { "" }
} else { "" }
}}
}
Which, it works, but it's just so much code and seeming like a whole lot of runtime overhead for no other reason than to organise some static values to avoid having to remember them all (in reality there are quite a lot and they're all raw codepoints). What I would love to be able to do is to just have a macro that takes a specific key/val and simply inlines either a known value or an empty value, but as far as I can tell there isn't any way to match a macro on a specific identifier, only any identifier... Is there any way that I can move all these lookups from runtime to compile time?
Macros can pattern match against specific identifiers — just don't use $.
macro_rules! get_value {
(some_key, a) => { Some('a') };
(some_key, b) => { Some('b') };
(some_key, c) => { None };
(some_other_key, a) => { Some(1) };
// ...
}
However, are you sure you don't want to just define a bunch of constants?
const SOME_KEY_A: Option<char> = Some('a');
const SOME_KEY_B: Option<char> = Some('b');
Or expose the Values struct you already designed, which would then be accessed like SOME_KEY.a:
const SOME_KEY: Values = Values {
a: Some('a'),
b: Some('b'),
c: None
};
That way, readers don't have to understand your macro to know that the data is just a constant. This will make your code easier to read and modify.

Assigning an additional type to an object property [duplicate]

Is there a way to change the type of interface property defined in a *.d.ts in typescript?
for example:
An interface in x.d.ts is defined as
interface A {
property: number;
}
I want to change it in the typescript files that I write to
interface A {
property: Object;
}
or even this would work
interface B extends A {
property: Object;
}
Will this approach work? It didn't work when I tried on my system. Just want to confirm if it's even possible?
I use a method that first filters the fields and then combines them.
reference Exclude property from type
interface A {
x: string
}
export type B = Omit<A, 'x'> & { x: number };
for interface:
interface A {
x: string
}
interface B extends Omit<A, 'x'> {
x: number
}
type ModifiedType = Modify<OriginalType, {
a: number;
b: number;
}>
interface ModifiedInterface extends Modify<OriginalType, {
a: number;
b: number;
}> {}
Inspired by ZSkycat's extends Omit solution, I came up with this:
type Modify<T, R> = Omit<T, keyof R> & R;
// before typescript#3.5
type Modify<T, R> = Pick<T, Exclude<keyof T, keyof R>> & R
Example:
interface OriginalInterface {
a: string;
b: boolean;
c: number;
}
type ModifiedType = Modify<OriginalInterface , {
a: number;
b: number;
}>
// ModifiedType = { a: number; b: number; c: number; }
Going step by step:
type R0 = Omit<OriginalType, 'a' | 'b'> // { c: number; }
type R1 = R0 & {a: number, b: number } // { a: number; b: number; c: number; }
type T0 = Exclude<'a' | 'b' | 'c' , 'a' | 'b'> // 'c'
type T1 = Pick<OriginalType, T0> // { c: number; }
type T2 = T1 & {a: number, b: number } // { a: number; b: number; c: number; }
TypeScript Utility Types
Deep Modification v3
interface Original {
a: {
a: string
b: { a: string }
c: string
d: string // <- keep this one
}
}
interface Overrides {
a: {
a: { a: number } // <- overwrite string with object
b: number // <- overwrite object with number
c: number // <- overwrite string with number
e: number // <- new property
}
}
type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
const example: ModifiedType = {
a: {
a: { a: number },
b: number,
c: number,
d: string,
e: number,
}
}
Find ModifyDeep below.
You can't change the type of an existing property.
You can add a property:
interface A {
newProperty: any;
}
But changing a type of existing one:
interface A {
property: any;
}
Results in an error:
Subsequent variable declarations must have the same type. Variable
'property' must be of type 'number', but here has type 'any'
You can of course have your own interface which extends an existing one. In that case, you can override a type only to a compatible type, for example:
interface A {
x: string | number;
}
interface B extends A {
x: number;
}
By the way, you probably should avoid using Object as a type, instead use the type any.
In the docs for the any type it states:
The any type is a powerful way to work with existing JavaScript,
allowing you to gradually opt-in and opt-out of type-checking during
compilation. You might expect Object to play a similar role, as it
does in other languages. But variables of type Object only allow you
to assign any value to them - you can’t call arbitrary methods on
them, even ones that actually exist:
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
The short answer for lazy people like me:
type Overrided = Omit<YourInterface, 'overrideField'> & { overrideField: <type> };
interface Overrided extends Omit<YourInterface, 'overrideField'> {
overrideField: <type>
}
Extending #zSkycat's answer a little, you can create a generic that accepts two object types and returns a merged type with the members of the second overriding the members of the first.
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;
interface A {
name: string;
color?: string;
}
// redefine name to be string | number
type B = Merge<A, {
name: string | number;
favorite?: boolean;
}>;
let one: A = {
name: 'asdf',
color: 'blue'
};
// A can become B because the types are all compatible
let two: B = one;
let three: B = {
name: 1
};
three.name = 'Bee';
three.favorite = true;
three.color = 'green';
// B cannot become A because the type of name (string | number) isn't compatible
// with A even though the value is a string
// Error: Type {...} is not assignable to type A
let four: A = three;
Omit the property when extending the interface:
interface A {
a: number;
b: number;
}
interface B extends Omit<A, 'a'> {
a: boolean;
}
I have created this type that allows me to easily override nested interfaces:
export type DeepPartialAny<T> = {
[P in keyof T]?: T[P] extends Obj ? DeepPartialAny<T[P]> : any;
};
export type Override<A extends Obj, AOverride extends DeepPartialAny<A>> = { [K in keyof A]:
AOverride[K] extends never
? A[K]
: AOverride[K] extends Obj
? Override<A[K], AOverride[K]>
: AOverride[K]
};
And then you can use it like that:
interface Foo {
Bar: {
Baz: string;
};
}
type Foo2 = Override<Foo, { Bar: { Baz: number } }>;
const bar: Foo2['Bar']['Baz'] = 1; // number;
For narrowing the type of the property, simple extend works perfect, as in Nitzan's answer:
interface A {
x: string | number;
}
interface B extends A {
x: number;
}
For widening, or generally overriding the type, you can do Zskycat's solution:
interface A {
x: string
}
export type B = Omit<A, 'x'> & { x: number };
But, if your interface A is extending a general interface, you will lose the custom types of A's remaining properties when using Omit.
e.g.
interface A extends Record<string | number, number | string | boolean> {
x: string;
y: boolean;
}
export type B = Omit<A, 'x'> & { x: number };
let b: B = { x: 2, y: "hi" }; // no error on b.y!
The reason is, Omit internally only goes over Exclude<keyof A, 'x'> keys which will be the general string | number in our case. So, B would become {x: number; } and accepts any extra property with the type of number | string | boolean.
To fix that, I came up with a different OverrideProps utility type as following:
type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };
Example:
type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };
interface A extends Record<string | number, number | string | boolean> {
x: string;
y: boolean;
}
export type B = OverrideProps<A, { x: number }>;
let b: B = { x: 2, y: "hi" }; // error: b.y should be boolean!
Date: 19/3/2021.
I think the latest typescript(4.1.2) version is supporting interface override in d.ts file.
// in test.d.ts
interface A {
a: string
}
export interface B extends A {
a: number
}
// in any ts file
import { B } from 'test.d.ts'
// this will work
const test: B = { a: 3 }
// this will not work
const test1: B = { a: "3" }
Override as Alias Type
You can use this type alias :
type Override<T, K extends { [P in keyof T]: any } | string> =
K extends string
? Omit<T, K>
: Omit<T, keyof K> & K;
and use alike below syntax :
Global Interface
interface IFirst {
username: string;
}
Interface by override the just name
interface ISecond extends Override<IFirst, 'username'> {
username: number;
}
Type alias override
type IThird = Override<IFirst, { username: boolean }>;
EDIT :
I'm tried to add this alias type as build-in type in typescript by send issue as proposal to the Typescript Repo
Solution for overwriting two or more properties of an interface:
interface Original {
a: string;
b: string;
c: string;
}
interface Modified extends Omit<Original, 'a' | 'b'> {
a?: string; // make it optional
b: boolean; // make it boolean
d: number; // add another property
}
From TypeScript documentation
It's funny I spend the day investigating possibility to solve the same case.
I found that it not possible doing this way:
// a.ts - module
export interface A {
x: string | any;
}
// b.ts - module
import {A} from './a';
type SomeOtherType = {
coolStuff: number
}
interface B extends A {
x: SomeOtherType;
}
Cause A module may not know about all available types in your application. And it's quite boring port everything from everywhere and doing code like this.
export interface A {
x: A | B | C | D ... Million Types Later
}
You have to define type later to have autocomplete works well.
So you can cheat a bit:
// a.ts - module
export interface A {
x: string;
}
Left the some type by default, that allow autocomplete works, when overrides not required.
Then
// b.ts - module
import {A} from './a';
type SomeOtherType = {
coolStuff: number
}
// #ts-ignore
interface B extends A {
x: SomeOtherType;
}
Disable stupid exception here using #ts-ignore flag, saying us the we doing something wrong. And funny thing everything works as expected.
In my case I'm reducing the scope vision of type x, its allow me doing code more stricted. For example you have list of 100 properties, and you reduce it to 10, to avoid stupid situations
If someone else needs a generic utility type to do this, I came up with the following solution:
/**
* Returns object T, but with T[K] overridden to type U.
* #example
* type MyObject = { a: number, b: string }
* OverrideProperty<MyObject, "a", string> // returns { a: string, b: string }
*/
export type OverrideProperty<T, K extends keyof T, U> = Omit<T, K> & { [P in keyof Pick<T, K>]: U };
I needed this because in my case, the key to override was a generic itself.
If you don't have Omit ready, see Exclude property from type.
If you only want to modify the type of an existing property and not remove it, then & is enough:
// Style that accepts both number and percent(string)
type BoxStyle = {
height?: string | number,
width?: string | number,
padding?: string | number,
borderRadius?: string | number,
}
// These are both valid
const box1: BoxStyle = {height: '20%', width: '20%', padding: 0, borderRadius: 5}
const box2: BoxStyle = {height: 85, width: 85, padding: 0, borderRadius: 5}
// Override height and width to be only numbers
type BoxStyleNumeric = BoxStyle & {
height?: number,
width?: number,
}
// This is still valid
const box3: BoxStyleNumeric = {height: 85, width: 85, padding: 0, borderRadius: 5}
// This is not valid anymore
const box4: BoxStyleNumeric = {height: '20%', width: '20%', padding: 0, borderRadius: 5}
Try this:
type Override<T extends object, K extends { [P in keyof T]?: any }> = Omit<T, keyof K> & K;
Usage:
type TransformedArticle = Override<Article, { id: string }>;
extending Qwerty's Modify utility type solution to restrict keys of R to ones present in T and add IntelliSense as well
export type Modify<T, R extends Partial<Record<keyof T, any>>> = Omit<T, keyof R> & R;
Deep modification v3
*note, version 2 is in the history of this answer.
interface Original {
a: {
a: string
b: { a: string }
c: string
d: string // <- keep this one
}
}
interface Overrides {
a: {
a: { a: number } // <- overwrite string with object
b: number // <- overwrite object with number
c: number // <- overwrite string with number
e: number // <- new property
}
}
type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
Result
const example: ModifiedType = {
a: {
a: { a: number },
b: number,
c: number,
d: string,
e: number,
}
}
The code
type ModifyDeep<A, B extends DeepPartialAny<A>> = {
[K in keyof A | keyof B]: // For all keys in A and B:
K extends keyof A // ───┐
? K extends keyof B // ───┼─ key K exists in both A and B
? A[K] extends AnyObject // │ ┴──┐
? B[K] extends AnyObject // │ ───┼─ both A and B are objects
? ModifyDeep<A[K], B[K]> // │ │ └─── We need to go deeper (recursively)
: B[K] // │ ├─ B is a primitive 🠆 use B as the final type (new type)
: B[K] // │ └─ A is a primitive 🠆 use B as the final type (new type)
: A[K] // ├─ key only exists in A 🠆 use A as the final type (original type)
: B[K] // └─ key only exists in B 🠆 use B as the final type (new type)
}
type AnyObject = Record<string, any>
// This type is here only for some intellisense for the overrides object
type DeepPartialAny<T> = {
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}
*Note, type DeepPartialAny is there just for type hints, but it's not perfect. Technically, the logic of the ModifyDeep type allows to replace leaf nodes {a: string} with objects {a: {b: ... }} and vice versa, but DeepPartialAny will complain when overriding an object with a flat primitive with an error such as this
Type 'number' has no properties in common with type 'DeepPartialAny<{ a: string; }>'
However, you can safely ignore the error (with /// #ts-ignore or remove extends DeepPartialAny constraint altogether. The resulting type is computed correctly anyway.
example
TypeScript Playground
type ModifyDeep<A, B extends DeepPartialAny<A>> = {
[K in keyof A | keyof B]:
K extends keyof A
? K extends keyof B
? A[K] extends AnyObject
? B[K] extends AnyObject
? ModifyDeep<A[K], B[K]>
: B[K]
: B[K]
: A[K]
: B[K]
}
type AnyObject = Record<string, any>
type DeepPartialAny<T> = {
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}
interface Original {
a: {
a: string
b: { a: string }
c: { a: string }
}
b: string
c: { a: string }
}
interface Overrides {
a: {
a: { a: number } // <- overwrite string with object
b: number // <- overwrite object with number
c: { b: number } // <- add new child property
d: number // <- new primitive property
}
d: { a: number } // <- new object property
}
//#ts-ignore // overriding an object with a flat value raises an error although the resulting type is calculated correctly
type ModifiedType = ModifyDeep<Original, Overrides>
//#ts-ignore
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
// Try modifying the properties here to prove that the type is working
const t: ModifiedType = {
a: {
a: { a: 0 },
b: 0,
c: { a: '', b: 0},
d: 0,
},
b: '',
c: { a: '' },
d: { a: 0 },
}
NOTE: Not sure if the syntax I'm using in this answer was available when the older answers were written, but I think that this is a better approach on how to solve the example mentioned in this question.
I've had some issues related to this topic (overwriting interface properties), and this is how I'm handling it:
First create a generic interface, with the possible types you'd like to use.
You can even use choose a default value for the generic parameter as you can see in <T extends number | SOME_OBJECT = number>
type SOME_OBJECT = { foo: "bar" }
interface INTERFACE_A <T extends number | SOME_OBJECT = number> {
property: T;
}
Then you can create new types based on that contract, by passing a value to the generic parameter (or omit it and use the default):
type A_NUMBER = INTERFACE_A; // USES THE default = number TYPE. SAME AS INTERFACE_A<number>
type A_SOME_OBJECT = INTERFACE_A<SOME_OBJECT> // MAKES { property: SOME_OBJECT }
And this is the result:
const aNumber: A_NUMBER = {
property: 111 // THIS EXPECTS A NUMBER
}
const anObject: A_SOME_OBJECT = {
property: { // THIS EXPECTS SOME_OBJECT
foo: "bar"
}
}
Typescript playground
Based on ZSkycat's excellent answer, you can create an abstracted Override generic type that is handy to use and explains clearly the intent of the code.
type Override<T, K extends keyof T, N> = Omit<T, K> & { [K1 in K]: N };
where:
T = existing type
K = key of type you wish to override
N = new type for key of existing type to override
Example usage:
type GraphQLCodegenConfig = Override<CodegenConfig, 'schema', DocumentNode>;
Create A modifier Type
type Modify<T, R extends {[P in keyof T]:any} > = Omit<T, keyof R> & R;
and you can
interface ModifiedInterface extends Modify<OriginalType, {
a: number;
b: number;
}> {}
it will give you a type autocomplete
Better solution would be to use below Modified type(pun intended) of this answer
export type Modify<T, R extends Partial<T>> = Omit<T, keyof R> & R;
This will also check that keys you are overriding is also present in the original interface and hence make sure that if original interface changes the name then you will get the compile time error and you also have to change the name.
Explanation:
Take the following example.
interface OriginalInterface {
id: string
}
and modified type is as below
interface ModifiedInterface {
id: number
}
Now, let say in future, OriginalInterface's id gets renamed to uId then using my type utility you will get the error as below
interface ModifiedInterface {
id: number // Type '{ geo_point1: GeoPoint | null; }' has no properties in common with type 'Partial<Address>'.ts(2559)
}

What is the proper way to traverse an "ADT" in Rust?

I want to implement a very basic lambda reducer. The first question that comes up is what sort of datatype does one use to implement the AST? In Haskell this would be an ADT. In Rust, it seems right is to use an enum and Boxes:
enum Term{
Num(i64),
Plus(Box<Term>, Box<Term>),
Var(i64),
Lam(i64, Box<Term>),
App(Box<Term>, Box<Term>)
}
This seems to be a good choice, but since I am a newb at Rust, it could very well be that the question I have -- that follows -- is simply that I have chosen the wrong datatype, and that, if I had chosen the right datatype representation, my problem would be gone. If this happens to be the case, please do let me know!
Now on to one step reduction. Following the Haskell code reference, we end up with something like:
fn reduceOneStep(t: Term) -> (Term, bool) {
match t {
Term::Num(a) => (t, false),
Term::Plus(t1, t2) =>
match (*t1, *t2) {
(Term::Num(a), Term::Num(b)) => (Term::Num(a + b), true),
(Term::Num(a), w) =>
match reduceOneStep(w) {
(t, b) => if b { (Term::Plus(t1, Box::new(t)), true) } else { (Term::Plus(t1, t2), false) }
},
_ => (Term::Num(1), false) //ignore .. this is just to satisfy typing and totality
},
x => (Term::Num(1), false) //ignore .. this is just to satisfy typing and totality
}
}
However, the line
(t, b) => if b { (Term::Plus(t1, Box::new(t)), true) } else { (Term::Plus(t1, t2), false) }
fails compilation. The reason is that I "used a moved value t1"
I don't really understand this error, nor how to get around it. I've tried some other variants, but they don't get around the problem. My question is: what am I doing wrong?
(Side note: this would probably better if you used the Result type instead of a tuple with a boolean inside it. I'll stick with the way you wrote it for this answer though.)
The error message says that you can't use the moved values t1 and t2, because those values were moved when you dereferenced them and did matching on them.
The information that was stored in t1 and t2 is now owned by the variables a, w, and t in that branch of the match expression, so you have to use those. If I'm wrong please correct me.
So you can go towards getting your example to work if, within the line:
(t, b) => if b { (Term::Plus(t1, Box::new(t)), true) } else { (Term::Plus(t1, t2), false) }
You replace t1 with Box::new(Term::Num(a)), and t2 with Box::new(w). That, with some indentation added, is:
(t, b) => if b {
(Term::Plus(Box::new(Term::Num(a)), Box::new(t)), true)
} else {
(Term::Plus(Box::new(Term::Num(a)), Box::new(w)), false)
}
This fails too though, because the call reduceOneStep(w) took ownership of w. That can be fixed by making reduceOneStep borrow its argument instead:
fn reduceOneStep(t: &Term) -> (Term, bool) {
match t {
&Term::Num(a) => (*t, false),
&Term::Plus(t1, t2) =>
match (*t1, *t2) {
(Term::Num(a), Term::Num(b)) => (Term::Num(a + b), true),
(Term::Num(a), w) =>
match reduceOneStep(&w) {
(t, b) => if b {
(Term::Plus(Box::new(Term::Num(a)), Box::new(t)), true)
} else {
(Term::Plus(Box::new(Term::Num(a)), Box::new(w)), false)
}
},
_ => (Term::Num(1), false) //ignore .. this is just to satisfy typing and totality
},
x => (Term::Num(1), false) //ignore .. this is just to satisfy typing and totality
}
}
But this has more errors, saying cannot move out of borrowed content, pointing to where it returns *t. This is because it can't both give the borrow back to the owner, and return a part of it as a result, because one could be freed and the other would be left hanging. One way to fix this is to #[derive(Clone)] for the Term enum, and use that:
fn reduceOneStep(t: &Term) -> (Term, bool) {
match t {
&Term::Num(a) => (t.clone(), false),
&Term::Plus(t1, t2) =>
match (*t1, *t2) {
(Term::Num(a), Term::Num(b)) => (Term::Num(a + b), true),
(Term::Num(a), w) =>
match reduceOneStep(&w) {
(t, b) => if b {
(Term::Plus(Box::new(Term::Num(a)), Box::new(t)), true)
} else {
(Term::Plus(Box::new(Term::Num(a)), Box::new(w.clone())), false)
}
},
_ => (Term::Num(1), false) //ignore .. this is just to satisfy typing and totality
},
x => (Term::Num(1), false) //ignore .. this is just to satisfy typing and totality
}
}
But this still has the same error. Huh. The error message has this hint below it: help: to prevent the move, use `ref t1` or `ref mut t1` to capture value by reference. Then after fixing some mismatched types and fiddling with box derefs and borrows, I finally got this to work:
fn reduceOneStep(t: &Term) -> (Term, bool) {
match t {
&Term::Num(a) => (t.clone(), false),
&Term::Plus(ref t1, ref t2) =>
match (&**t1, &**t2) {
(&Term::Num(a), &Term::Num(b)) => (Term::Num(a + b), true),
(&Term::Num(a), w) =>
match reduceOneStep(&w) {
(t, b) => if b {
(Term::Plus(Box::new(Term::Num(a)), Box::new(t)), true)
} else {
(Term::Plus(Box::new(Term::Num(a)), Box::new(w.clone())), false)
}
},
_ => (Term::Num(1), false) //ignore .. this is just to satisfy typing and totality
},
x => (Term::Num(1), false) //ignore .. this is just to satisfy typing and totality
}
}
I'm a beginner with rust right now, so if someone can help me understand why this works, I'll be very grateful.

Is there a non-messy way to chain the results of functions that return Option values?

I have some code that looks like this:
f(a).and_then(|b| {
g(b).and_then(|c| {
h(c).map(|d| {
do_something_with(a, b, c, d)
})
})
})
Where f, g, and h return Option values. I need to use all the intermediate values (a, b, c, and d) in the do_something_with calculation. The indentation is very deep. Is there a better way to do this? Ideally it would look something like this (which of course doesn't work):
try {
let b = f(a);
let c = g(b);
let d = h(c);
do_something_with(a, b, c, d)
} rescue NonexistentValueException {
None
}
Rust 1.22
The question mark operator now supports Option, so you can write your function as
fn do_something(a: i32) -> Option<i32> {
let b = f(a)?;
let c = g(b)?;
let d = h(c)?;
do_something_with(a, b, c, d) // wrap in Some(...) if this doesn't return an Option
}
Rust 1.0
The Rust standard library defines a try! macro (and, equivalently, the ? operator, as of Rust 1.13) that solves this problem for Result. The macro looks like this:
macro_rules! try {
($expr:expr) => (match $expr {
$crate::result::Result::Ok(val) => val,
$crate::result::Result::Err(err) => {
return $crate::result::Result::Err($crate::convert::From::from(err))
}
})
}
If the argument is Err, it returns from the function with that Err value. Otherwise, it evaluates to the value wrapped in Ok. The macro can only be used in a function that returns Result, because it returns the error it meets.
We can make a similar macro for Option:
macro_rules! try_opt {
($expr:expr) => (match $expr {
::std::option::Option::Some(val) => val,
::std::option::Option::None => return None
})
}
You can then use this macro like this:
fn do_something(a: i32) -> Option<i32> {
let b = try_opt!(f(a));
let c = try_opt!(g(b));
let d = try_opt!(h(c));
do_something_with(a, b, c, d) // wrap in Some(...) if this doesn't return an Option
}
Inspired from the concept of try! for Result, let's wrap our own macro to early-return from the scope if the monad drops to None.
macro_rules! get(
($e:expr) => (match $e { Some(e) => e, None => return None })
);
(Stolen from this reddit thread)
Now you can run your code linearly:
fn blah() -> Option<...> { // ... is the return type of do_something_with()
let a = 123;
let b = get!(f(a));
let c = get!(g(b));
let d = get!(h(c));
do_something_with(a, b, c, d)
}
(runnable gist)

Resources