diff --git a/theories/utils/switch.v b/theories/utils/switch.v
new file mode 100644
index 0000000000000000000000000000000000000000..ac7ea537d91ca8ad102bb0491232c0436a73f1a3
--- /dev/null
+++ b/theories/utils/switch.v
@@ -0,0 +1,97 @@
+From stdpp Require Import pretty.
+From iris.heap_lang Require Export lang.
+From iris.heap_lang Require Import metatheory proofmode notation assert.
+Set Default Proof Using "Type".
+
+Fixpoint switch_body (i : nat) (xs : list Z) (e : expr) : expr :=
+  match xs with
+  | [] => e
+  | x :: xs => if: "y" = #x then ("f" +:+ pretty i) #()
+               else switch_body (S i) xs e
+  end.
+Fixpoint switch_lams (y : string) (i : nat) (n : nat) (e : expr) : expr :=
+  match n with
+  | O => e
+  | S n => λ: (y +:+ pretty i), switch_lams y (S i) n e
+  end.
+Definition switch_fail (xs : list Z) : val :=
+  λ: "y", switch_lams "f" 0 (length xs) (switch_body 0 xs (assert: #false)).
+
+Fixpoint map_string_seq {A}
+    (s : string) (start : nat) (xs : list A) : gmap string A :=
+  match xs with
+  | [] => ∅
+  | x :: xs => <[s+:+pretty start:=x]> (map_string_seq s (S start) xs)
+  end.
+
+Lemma lookup_map_string_seq_Some {A} (j i : nat) (xs : list A) :
+  map_string_seq "f" j xs !! ("f" +:+ pretty (i + j)%nat) = xs !! i.
+Proof.
+  revert i j. induction xs as [|x xs IH]=> -[|i] j //=.
+  - by rewrite lookup_insert.
+  - rewrite lookup_insert_ne; last (intros ?; simplify_eq/=; lia).
+    by rewrite -Nat.add_succ_r IH.
+Qed.
+
+Lemma lookup_map_string_seq_None {A} y j z (vs : list A) :
+  (∀ i, y +:+ pretty i ≠ z) →
+  map_string_seq y j vs !! z = None.
+Proof.
+  intros. revert j. induction vs as [|v vs IH]=> j //=.
+  by rewrite lookup_insert_ne.
+Qed.
+
+Lemma lookup_map_string_seq_None_lt {A} y i j (xs : list A) :
+  (i < j)%nat → map_string_seq y j xs !! (y +:+ pretty i) = None.
+Proof.
+  revert j. induction xs as [|x xs IH]=> j ? //=.
+  rewrite lookup_insert_ne; last (intros ?; simplify_eq/=; lia).
+  apply IH. lia.
+Qed.
+
+Lemma switch_lams_spec `{heapG Σ} y i n ws vs e Φ :
+  length vs = n →
+  WP subst_map (map_string_seq y i vs ∪ ws) e {{ Φ }} -∗
+  WP subst_map ws (switch_lams y i n e) {{ w, WP fill (AppLCtx <$> vs) w {{ Φ }} }}.
+Proof.
+  iIntros (<-) "H".
+  iInduction vs as [|v vs] "IH" forall (i ws); csimpl.
+  { rewrite left_id_L.
+    iApply (wp_wand with "H"); iIntros (v) "H". by iApply wp_value. }
+  wp_pures. iApply wp_bind.
+  iEval (rewrite -subst_map_insert insert_union_singleton_l).
+  iApply "IH". rewrite assoc_L insert_union_singleton_r //
+    lookup_map_string_seq_None_lt; auto with lia.
+Qed.
+
+Lemma switch_body_spec `{heapG Σ} xs i j ws (x : Z) (f : val) e Φ :
+  fst <$> list_find (x =.) xs = Some i →
+  ws !! "y" = Some #x →
+  ws !! ("f" +:+ pretty (i + j)%nat) = Some f →
+  WP f #() {{ Φ }} -∗
+  WP subst_map ws (switch_body j xs e) {{ Φ }}.
+Proof.
+  iIntros (Hi Hy Hf) "H".
+  iInduction xs as [|x' xs] "IH" forall (i j Hi Hf); simplify_eq/=.
+  rewrite Hy. case_decide; simplify_eq/=; wp_pures.
+  { rewrite bool_decide_true; last congruence. wp_pures. by rewrite Hf. }
+  move: Hi=> /fmap_Some [[??] [/fmap_Some [[i' x''] [??]] ?]]; simplify_eq/=.
+  rewrite bool_decide_false; last congruence. wp_pures.
+  iApply ("IH" $! i' with "[%] [%] H").
+  - by simplify_option_eq.
+  - by rewrite Nat.add_succ_r.
+Qed.
+
+Lemma switch_fail_spec `{heapG Σ} fs xs i (x : Z) (f : val) Φ :
+  length fs = length xs →
+  fst <$> list_find (x =.) xs = Some i →
+  fs !! i = Some f →
+  ▷ WP f #() {{ Φ }} -∗
+  WP fill (AppLCtx <$> fs) (switch_fail xs #x) {{ Φ }}.
+Proof.
+  iIntros (???) "H". iApply wp_bind. wp_lam.
+  rewrite -subst_map_singleton. iApply switch_lams_spec; first done.
+  iApply (switch_body_spec with "H"); [done|..].
+  - by rewrite lookup_union_r ?lookup_singleton // lookup_map_string_seq_None.
+  - apply lookup_union_Some_l. by rewrite lookup_map_string_seq_Some.
+Qed.