Lower std constructors; "unknown function" was mostly not dependencies
Twice I described this category as "calls into dependencies" without opening it, and twice that was wrong. The most common name by far was `new`, and the calls were Vec::new(), String::new(), Vec::with_capacity() and size_of::<T>() -- std's own surface, each with an exact Nim counterpart. Those are lowered now, together with mem::swap and Default for primitives. One detail is easy to get wrong and is pinned by tests/cases/043: with_capacity reserves without adding elements, which is newSeqOfCap. newSeq would create that many zeroed elements and len() would not match -- right shape, wrong answer. That took the category from 62 to 54. What remains divides cleanly into three groups, and DESIGN.md records them because the distinction matters more than the count: std concurrency (Arc, Mutex, AtomicUsize, Pin), dependency types (Aes256Gcm::new, Bytes::new), and platform calls (env::var_os, getauxval). The concurrency group is not a mapping table. Arc is shared ownership, which Nim's GC'd ref gives, but Arc is atomically refcounted where a plain ref is not. Mutex needs the guard's Drop to release at scope exit. AtomicUsize needs memory orderings and would be indistinguishable from a plain variable in a program with no threads -- which cannot be verified here, since std::thread is not modelled at all. Each is a design question about semantics rather than a syntactic gap, so none is mapped casually. Accepted crates unchanged at 18 of 400, Nim-compilable at 12. 49 differential cases, all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
edb7777 parent: 911457b modified
DESIGN.md +35 -0 | @@ -826,6 +826,41 @@ resolve — `overloaded 'rsDefault_default' leads to ambiguous calls`. Those | ||
| 826 | 826 | keep the type in the name. The `bitflags!` shim's operators were renamed to |
| 827 | 827 | match, since they are trait methods with a receiver. |
| 828 | 828 | |
| 829 | +## What "call to unknown function" was hiding | |
| 830 | + | |
| 831 | +Twice I characterised this category as "calls into dependencies" without | |
| 832 | +opening it, and twice that was wrong. Opening it: the most common name by far | |
| 833 | +was `new` — and the calls were `Vec::new()`, `String::new()`, | |
| 834 | +`Vec::with_capacity()`, `size_of::<T>()`. Not dependencies at all, but | |
| 835 | +**`std`'s own surface**, each with an exact Nim counterpart. | |
| 836 | + | |
| 837 | +Those are now lowered, with one detail that is easy to get wrong: | |
| 838 | +`with_capacity` reserves without adding elements, which is `newSeqOfCap`. | |
| 839 | +`newSeq` would create that many zeroed elements and `len()` would not match. | |
| 840 | +`tests/cases/043` checks exactly that. | |
| 841 | + | |
| 842 | +That took the category from 62 to 54, and what is left divides cleanly: | |
| 843 | + | |
| 844 | +| | | | | |
| 845 | +|---|---|---| | |
| 846 | +| **std concurrency** | `Arc`, `Mutex`, `AtomicUsize`, `AtomicBool`, `Pin` | semantically loaded | | |
| 847 | +| **dependency types** | `Aes256Gcm::new`, `GHash::new`, `Bytes::new` | the closure problem | | |
| 848 | +| **platform** | `env::var_os`, `getauxval` | `std::env`, `libc` | | |
| 849 | + | |
| 850 | +The concurrency group is the interesting one, and it is *not* a mapping table. | |
| 851 | +`Arc<T>` is shared ownership, which Nim's GC'd `ref` gives — but `Arc` is | |
| 852 | +atomically refcounted where a plain Nim `ref` is not. `Mutex<T>` needs the | |
| 853 | +guard's `Drop` to release the lock at scope exit. `AtomicUsize` needs memory | |
| 854 | +orderings, and would be indistinguishable from a plain variable in a program | |
| 855 | +with no threads — which we cannot verify, since `std::thread` is not modelled | |
| 856 | +at all. Each of those is a design question about semantics, not a syntactic | |
| 857 | +gap, and none should be mapped casually. | |
| 858 | + | |
| 859 | +So the honest statement about this category is neither "bounded and cheap" nor | |
| 860 | +"unbounded tail": part of it was cheap std surface and is done, and the rest | |
| 861 | +is a small number of semantically heavy primitives plus the dependency | |
| 862 | +closure. | |
| 863 | + | |
| 829 | 864 | ## Proof of byte-identity for `base16ct` |
| 830 | 865 | |
| 831 | 866 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive |
| @@ -826,6 +826,41 @@ resolve — `overloaded 'rsDefault_default' leads to ambiguous calls`. Those | |||
| 826 | keep the type in the name. The `bitflags!` shim's operators were renamed to | 826 | keep the type in the name. The `bitflags!` shim's operators were renamed to |
| 827 | match, since they are trait methods with a receiver. | 827 | match, since they are trait methods with a receiver. |
| 828 | 828 | ||
| 829 | +## What "call to unknown function" was hiding | ||
| 830 | + | ||
| 831 | +Twice I characterised this category as "calls into dependencies" without | ||
| 832 | +opening it, and twice that was wrong. Opening it: the most common name by far | ||
| 833 | +was `new` — and the calls were `Vec::new()`, `String::new()`, | ||
| 834 | +`Vec::with_capacity()`, `size_of::<T>()`. Not dependencies at all, but | ||
| 835 | +**`std`'s own surface**, each with an exact Nim counterpart. | ||
| 836 | + | ||
| 837 | +Those are now lowered, with one detail that is easy to get wrong: | ||
| 838 | +`with_capacity` reserves without adding elements, which is `newSeqOfCap`. | ||
| 839 | +`newSeq` would create that many zeroed elements and `len()` would not match. | ||
| 840 | +`tests/cases/043` checks exactly that. | ||
| 841 | + | ||
| 842 | +That took the category from 62 to 54, and what is left divides cleanly: | ||
| 843 | + | ||
| 844 | +| | | | | ||
| 845 | +|---|---|---| | ||
| 846 | +| **std concurrency** | `Arc`, `Mutex`, `AtomicUsize`, `AtomicBool`, `Pin` | semantically loaded | | ||
| 847 | +| **dependency types** | `Aes256Gcm::new`, `GHash::new`, `Bytes::new` | the closure problem | | ||
| 848 | +| **platform** | `env::var_os`, `getauxval` | `std::env`, `libc` | | ||
| 849 | + | ||
| 850 | +The concurrency group is the interesting one, and it is *not* a mapping table. | ||
| 851 | +`Arc<T>` is shared ownership, which Nim's GC'd `ref` gives — but `Arc` is | ||
| 852 | +atomically refcounted where a plain Nim `ref` is not. `Mutex<T>` needs the | ||
| 853 | +guard's `Drop` to release the lock at scope exit. `AtomicUsize` needs memory | ||
| 854 | +orderings, and would be indistinguishable from a plain variable in a program | ||
| 855 | +with no threads — which we cannot verify, since `std::thread` is not modelled | ||
| 856 | +at all. Each of those is a design question about semantics, not a syntactic | ||
| 857 | +gap, and none should be mapped casually. | ||
| 858 | + | ||
| 859 | +So the honest statement about this category is neither "bounded and cheap" nor | ||
| 860 | +"unbounded tail": part of it was cheap std surface and is done, and the rest | ||
| 861 | +is a small number of semantically heavy primitives plus the dependency | ||
| 862 | +closure. | ||
| 863 | + | ||
| 829 | ## Proof of byte-identity for `base16ct` | 864 | ## Proof of byte-identity for `base16ct` |
| 830 | 865 | ||
| 831 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive | 866 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive |
modified
src/lower.rs +103 -0 | @@ -4716,6 +4716,97 @@ impl Lowerer { | ||
| 4716 | 4716 | } |
| 4717 | 4717 | } |
| 4718 | 4718 | |
| 4719 | + // Standard-library constructors. These are not calls into a | |
| 4720 | + // dependency -- they are `std`'s own surface, and each has an exact | |
| 4721 | + // Nim counterpart rather than an approximate one. | |
| 4722 | + let qual = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()); | |
| 4723 | + match (qual.as_deref(), name.as_str(), codes.len()) { | |
| 4724 | + (Some("Vec"), "new", 0) | (Some("Vec"), "with_capacity", 1) => { | |
| 4725 | + let elem = match expect { | |
| 4726 | + Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => (**e).clone(), | |
| 4727 | + _ => { | |
| 4728 | + return Err( | |
| 4729 | + "`Vec::new()` needs a known element type; annotate the \ | |
| 4730 | + binding" | |
| 4731 | + .into(), | |
| 4732 | + ) | |
| 4733 | + } | |
| 4734 | + }; | |
| 4735 | + let t = Nim::Seq(Box::new(elem.clone())); | |
| 4736 | + return Ok(Val::new( | |
| 4737 | + if codes.is_empty() { | |
| 4738 | + format!("newSeq[{}]()", elem.render()) | |
| 4739 | + } else { | |
| 4740 | + // `with_capacity` reserves without adding elements, | |
| 4741 | + // which is what `newSeqOfCap` does; `newSeq` would | |
| 4742 | + // create that many zeroed ones. | |
| 4743 | + format!("newSeqOfCap[{}](int({}))", elem.render(), codes[0]) | |
| 4744 | + }, | |
| 4745 | + Some(t), | |
| 4746 | + )); | |
| 4747 | + } | |
| 4748 | + (Some("String"), "new", 0) => { | |
| 4749 | + return Ok(Val::new("\"\"", Some(Nim::Prim("string".into())))) | |
| 4750 | + } | |
| 4751 | + (Some("String"), "with_capacity", 1) => { | |
| 4752 | + return Ok(Val::new( | |
| 4753 | + format!("newStringOfCap(int({}))", codes[0]), | |
| 4754 | + Some(Nim::Prim("string".into())), | |
| 4755 | + )) | |
| 4756 | + } | |
| 4757 | + (_, "size_of" | "size_of_val", _) => { | |
| 4758 | + // `size_of::<T>()` -- the width is the platform's, and both | |
| 4759 | + // languages ask the same C compiler for it. | |
| 4760 | + let t = p | |
| 4761 | + .path | |
| 4762 | + .segments | |
| 4763 | + .last() | |
| 4764 | + .and_then(|seg| match &seg.arguments { | |
| 4765 | + syn::PathArguments::AngleBracketed(a) => a.args.iter().find_map(|g| { | |
| 4766 | + match g { | |
| 4767 | + GenericArgument::Type(t) => Some(t.clone()), | |
| 4768 | + _ => None, | |
| 4769 | + } | |
| 4770 | + }), | |
| 4771 | + _ => None, | |
| 4772 | + }); | |
| 4773 | + let inner = match (t, args.first()) { | |
| 4774 | + (Some(t), _) => self.map_ty(&t)?.render(), | |
| 4775 | + (None, Some(a)) => a | |
| 4776 | + .ty | |
| 4777 | + .clone() | |
| 4778 | + .ok_or("`size_of_val` needs a known argument type")? | |
| 4779 | + .render(), | |
| 4780 | + _ => return Err("`size_of` needs a type argument".into()), | |
| 4781 | + }; | |
| 4782 | + return Ok(Val::new( | |
| 4783 | + format!("uint(sizeof({}))", inner), | |
| 4784 | + Some(Nim::Prim("uint".into())), | |
| 4785 | + )); | |
| 4786 | + } | |
| 4787 | + (Some("mem"), "swap", 2) => { | |
| 4788 | + return Ok(Val::new( | |
| 4789 | + format!("swap({}, {})", codes[0], codes[1]), | |
| 4790 | + Some(Nim::Unit), | |
| 4791 | + )) | |
| 4792 | + } | |
| 4793 | + _ => {} | |
| 4794 | + } | |
| 4795 | + | |
| 4796 | + // `T::default()` for a primitive: Rust's `Default` for these is the | |
| 4797 | + // zero value, which is also Nim's. | |
| 4798 | + if name == "default" && codes.is_empty() { | |
| 4799 | + let t = qual | |
| 4800 | + .as_deref() | |
| 4801 | + .and_then(ty::prim) | |
| 4802 | + .or_else(|| expect.cloned()); | |
| 4803 | + if let Some(t) = t { | |
| 4804 | + if let Some(code) = default_of(&t) { | |
| 4805 | + return Ok(Val::new(code, Some(t))); | |
| 4806 | + } | |
| 4807 | + } | |
| 4808 | + } | |
| 4809 | + | |
| 4719 | 4810 | // `block_on(f)` — whichever executor it came from, the operation is |
| 4720 | 4811 | // the same: run the future to completion and give back its output. |
| 4721 | 4812 | if name == "block_on" && codes.len() == 1 { |
| @@ -5782,6 +5873,18 @@ fn module_of(prefix: &[String]) -> String { | ||
| 5782 | 5873 | } |
| 5783 | 5874 | } |
| 5784 | 5875 | |
| 5876 | +/// Rust's `Default` for a primitive is its zero value, which is Nim's too. | |
| 5877 | +fn default_of(t: &Nim) -> Option<String> { | |
| 5878 | + Some(match t { | |
| 5879 | + Nim::Prim(p) if p == "string" => "\"\"".into(), | |
| 5880 | + Nim::Prim(p) if p == "bool" => "false".into(), | |
| 5881 | + Nim::Prim(p) if p.starts_with("float") => format!("{}(0.0)", p), | |
| 5882 | + Nim::Prim(_) if t.is_integer() => format!("{}(0)", t.render()), | |
| 5883 | + Nim::Seq(e) => format!("newSeq[{}]()", e.render()), | |
| 5884 | + _ => return None, | |
| 5885 | + }) | |
| 5886 | +} | |
| 5887 | + | |
| 5785 | 5888 | /// The first type argument of an `Option[T]` / `Result[T, E]`. |
| 5786 | 5889 | fn elem_arg(t: &Nim) -> Nim { |
| 5787 | 5890 | match t { |
| @@ -4716,6 +4716,97 @@ impl Lowerer { | |||
| 4716 | } | 4716 | } |
| 4717 | } | 4717 | } |
| 4718 | 4718 | ||
| 4719 | + // Standard-library constructors. These are not calls into a | ||
| 4720 | + // dependency -- they are `std`'s own surface, and each has an exact | ||
| 4721 | + // Nim counterpart rather than an approximate one. | ||
| 4722 | + let qual = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()); | ||
| 4723 | + match (qual.as_deref(), name.as_str(), codes.len()) { | ||
| 4724 | + (Some("Vec"), "new", 0) | (Some("Vec"), "with_capacity", 1) => { | ||
| 4725 | + let elem = match expect { | ||
| 4726 | + Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => (**e).clone(), | ||
| 4727 | + _ => { | ||
| 4728 | + return Err( | ||
| 4729 | + "`Vec::new()` needs a known element type; annotate the \ | ||
| 4730 | + binding" | ||
| 4731 | + .into(), | ||
| 4732 | + ) | ||
| 4733 | + } | ||
| 4734 | + }; | ||
| 4735 | + let t = Nim::Seq(Box::new(elem.clone())); | ||
| 4736 | + return Ok(Val::new( | ||
| 4737 | + if codes.is_empty() { | ||
| 4738 | + format!("newSeq[{}]()", elem.render()) | ||
| 4739 | + } else { | ||
| 4740 | + // `with_capacity` reserves without adding elements, | ||
| 4741 | + // which is what `newSeqOfCap` does; `newSeq` would | ||
| 4742 | + // create that many zeroed ones. | ||
| 4743 | + format!("newSeqOfCap[{}](int({}))", elem.render(), codes[0]) | ||
| 4744 | + }, | ||
| 4745 | + Some(t), | ||
| 4746 | + )); | ||
| 4747 | + } | ||
| 4748 | + (Some("String"), "new", 0) => { | ||
| 4749 | + return Ok(Val::new("\"\"", Some(Nim::Prim("string".into())))) | ||
| 4750 | + } | ||
| 4751 | + (Some("String"), "with_capacity", 1) => { | ||
| 4752 | + return Ok(Val::new( | ||
| 4753 | + format!("newStringOfCap(int({}))", codes[0]), | ||
| 4754 | + Some(Nim::Prim("string".into())), | ||
| 4755 | + )) | ||
| 4756 | + } | ||
| 4757 | + (_, "size_of" | "size_of_val", _) => { | ||
| 4758 | + // `size_of::<T>()` -- the width is the platform's, and both | ||
| 4759 | + // languages ask the same C compiler for it. | ||
| 4760 | + let t = p | ||
| 4761 | + .path | ||
| 4762 | + .segments | ||
| 4763 | + .last() | ||
| 4764 | + .and_then(|seg| match &seg.arguments { | ||
| 4765 | + syn::PathArguments::AngleBracketed(a) => a.args.iter().find_map(|g| { | ||
| 4766 | + match g { | ||
| 4767 | + GenericArgument::Type(t) => Some(t.clone()), | ||
| 4768 | + _ => None, | ||
| 4769 | + } | ||
| 4770 | + }), | ||
| 4771 | + _ => None, | ||
| 4772 | + }); | ||
| 4773 | + let inner = match (t, args.first()) { | ||
| 4774 | + (Some(t), _) => self.map_ty(&t)?.render(), | ||
| 4775 | + (None, Some(a)) => a | ||
| 4776 | + .ty | ||
| 4777 | + .clone() | ||
| 4778 | + .ok_or("`size_of_val` needs a known argument type")? | ||
| 4779 | + .render(), | ||
| 4780 | + _ => return Err("`size_of` needs a type argument".into()), | ||
| 4781 | + }; | ||
| 4782 | + return Ok(Val::new( | ||
| 4783 | + format!("uint(sizeof({}))", inner), | ||
| 4784 | + Some(Nim::Prim("uint".into())), | ||
| 4785 | + )); | ||
| 4786 | + } | ||
| 4787 | + (Some("mem"), "swap", 2) => { | ||
| 4788 | + return Ok(Val::new( | ||
| 4789 | + format!("swap({}, {})", codes[0], codes[1]), | ||
| 4790 | + Some(Nim::Unit), | ||
| 4791 | + )) | ||
| 4792 | + } | ||
| 4793 | + _ => {} | ||
| 4794 | + } | ||
| 4795 | + | ||
| 4796 | + // `T::default()` for a primitive: Rust's `Default` for these is the | ||
| 4797 | + // zero value, which is also Nim's. | ||
| 4798 | + if name == "default" && codes.is_empty() { | ||
| 4799 | + let t = qual | ||
| 4800 | + .as_deref() | ||
| 4801 | + .and_then(ty::prim) | ||
| 4802 | + .or_else(|| expect.cloned()); | ||
| 4803 | + if let Some(t) = t { | ||
| 4804 | + if let Some(code) = default_of(&t) { | ||
| 4805 | + return Ok(Val::new(code, Some(t))); | ||
| 4806 | + } | ||
| 4807 | + } | ||
| 4808 | + } | ||
| 4809 | + | ||
| 4719 | // `block_on(f)` — whichever executor it came from, the operation is | 4810 | // `block_on(f)` — whichever executor it came from, the operation is |
| 4720 | // the same: run the future to completion and give back its output. | 4811 | // the same: run the future to completion and give back its output. |
| 4721 | if name == "block_on" && codes.len() == 1 { | 4812 | if name == "block_on" && codes.len() == 1 { |
| @@ -5782,6 +5873,18 @@ fn module_of(prefix: &[String]) -> String { | |||
| 5782 | } | 5873 | } |
| 5783 | } | 5874 | } |
| 5784 | 5875 | ||
| 5876 | +/// Rust's `Default` for a primitive is its zero value, which is Nim's too. | ||
| 5877 | +fn default_of(t: &Nim) -> Option<String> { | ||
| 5878 | + Some(match t { | ||
| 5879 | + Nim::Prim(p) if p == "string" => "\"\"".into(), | ||
| 5880 | + Nim::Prim(p) if p == "bool" => "false".into(), | ||
| 5881 | + Nim::Prim(p) if p.starts_with("float") => format!("{}(0.0)", p), | ||
| 5882 | + Nim::Prim(_) if t.is_integer() => format!("{}(0)", t.render()), | ||
| 5883 | + Nim::Seq(e) => format!("newSeq[{}]()", e.render()), | ||
| 5884 | + _ => return None, | ||
| 5885 | + }) | ||
| 5886 | +} | ||
| 5887 | + | ||
| 5785 | /// The first type argument of an `Option[T]` / `Result[T, E]`. | 5888 | /// The first type argument of an `Option[T]` / `Result[T, E]`. |
| 5786 | fn elem_arg(t: &Nim) -> Nim { | 5889 | fn elem_arg(t: &Nim) -> Nim { |
| 5787 | match t { | 5890 | match t { |
added
tests/cases/043-std-constructors.rs +35 -0 | new file mode 100644 | ||
| @@ -0,0 +1,35 @@ | ||
| 1 | +// These are `std`'s own surface rather than calls into a dependency, and each | |
| 2 | +// has an exact Nim counterpart. `with_capacity` reserves without adding | |
| 3 | +// elements, which is `newSeqOfCap`; `newSeq` would create that many zeroed | |
| 4 | +// ones and `len()` would not match. | |
| 5 | + | |
| 6 | +use core::mem::size_of; | |
| 7 | + | |
| 8 | +fn main() { | |
| 9 | + let mut v: Vec<i32> = Vec::new(); | |
| 10 | + println!("{} {}", v.len(), v.is_empty()); | |
| 11 | + v.push(3); | |
| 12 | + v.push(4); | |
| 13 | + println!("{:?} {}", v, v.len()); | |
| 14 | + | |
| 15 | + let w: Vec<u8> = Vec::with_capacity(16); | |
| 16 | + println!("{} {}", w.len(), w.is_empty()); | |
| 17 | + | |
| 18 | + let s = String::new(); | |
| 19 | + println!("[{}] {}", s, s.len()); | |
| 20 | + let t = String::with_capacity(32); | |
| 21 | + println!("[{}] {}", t, t.len()); | |
| 22 | + | |
| 23 | + println!("{} {} {} {}", size_of::<u8>(), size_of::<u32>(), size_of::<i64>(), size_of::<f64>()); | |
| 24 | + | |
| 25 | + let a: i32 = i32::default(); | |
| 26 | + let b: bool = bool::default(); | |
| 27 | + let c: f64 = f64::default(); | |
| 28 | + let d: String = String::default(); | |
| 29 | + println!("{} {} {} [{}]", a, b, c, d); | |
| 30 | + | |
| 31 | + let mut x: i32 = 1; | |
| 32 | + let mut y: i32 = 2; | |
| 33 | + core::mem::swap(&mut x, &mut y); | |
| 34 | + println!("{} {}", x, y); | |
| 35 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | +// These are `std`'s own surface rather than calls into a dependency, and each | ||
| 2 | +// has an exact Nim counterpart. `with_capacity` reserves without adding | ||
| 3 | +// elements, which is `newSeqOfCap`; `newSeq` would create that many zeroed | ||
| 4 | +// ones and `len()` would not match. | ||
| 5 | + | ||
| 6 | +use core::mem::size_of; | ||
| 7 | + | ||
| 8 | +fn main() { | ||
| 9 | + let mut v: Vec<i32> = Vec::new(); | ||
| 10 | + println!("{} {}", v.len(), v.is_empty()); | ||
| 11 | + v.push(3); | ||
| 12 | + v.push(4); | ||
| 13 | + println!("{:?} {}", v, v.len()); | ||
| 14 | + | ||
| 15 | + let w: Vec<u8> = Vec::with_capacity(16); | ||
| 16 | + println!("{} {}", w.len(), w.is_empty()); | ||
| 17 | + | ||
| 18 | + let s = String::new(); | ||
| 19 | + println!("[{}] {}", s, s.len()); | ||
| 20 | + let t = String::with_capacity(32); | ||
| 21 | + println!("[{}] {}", t, t.len()); | ||
| 22 | + | ||
| 23 | + println!("{} {} {} {}", size_of::<u8>(), size_of::<u32>(), size_of::<i64>(), size_of::<f64>()); | ||
| 24 | + | ||
| 25 | + let a: i32 = i32::default(); | ||
| 26 | + let b: bool = bool::default(); | ||
| 27 | + let c: f64 = f64::default(); | ||
| 28 | + let d: String = String::default(); | ||
| 29 | + println!("{} {} {} [{}]", a, b, c, d); | ||
| 30 | + | ||
| 31 | + let mut x: i32 = 1; | ||
| 32 | + let mut y: i32 = 2; | ||
| 33 | + core::mem::swap(&mut x, &mut y); | ||
| 34 | + println!("{} {}", x, y); | ||
| 35 | +} | ||