Skip to content

Visual Studio Code Rust Analyzer

Sewen Thy edited this page Sep 21, 2022 · 3 revisions

This document outlines how Visual Studio Code's Rust Analyzer which works on LSP handles the toy examples in the criterion for refactoring page.

Non-local control flows

pub fn original_foo() -> String {
    let x = '1';
    let y = if x > '2' {
        x
    } else {
        return String::from("y")
    };
    String::from(y)
}

Rust Analyzer used the Result container and did the extraction as described in the correction to IntelliJ's extraction:

pub fn new_foo() -> String {
    let x = '1';
    let y = match bar_extracted(x) {
        Ok(value) => value,
        Err(value) => return value,
    };
    String::from(y)
}

fn bar_extracted(x: char) -> Result<char, String> {
    Ok(if x > '2' {
        x
    } else {
        return Err(String::from("y"))
    })
}

Different in/out lifetime

const W: i32 = 5; // 'static

pub fn original_foo () { // scope a
    let x = 1;
    let x_ref = &x; // 'a
    let mut z : &i32; // 'a
    { // scope b
        let y = 2;
        z = &y; // 'b
        z = if *z < *x_ref {
            &y // 'b
        } else {
            &W // 'static
        };
        println!("{}", *z);
    }
}

For this particular extraction, Rust Analyzer did not account for lifetime at all:

pub fn new_foo () {
    let x = 1;
    let x_ref = &x;
    let mut z : &i32;
    {
        let y = 2;
        z = &y;
        z = bar_extracted(z, x_ref, y);
        println!("{}", *z);
    }
}

fn bar_extracted(z: &i32, x_ref: &i32, y: i32) -> &i32 {
    if *z < *x_ref {
        &y
    } else {
        &W
    }
}

However, Rust Analyzer managed to show the error:
error-message-rust-analyzer-on-in-out-lifetime

Furthermore, this is also a flawed way of extracting since the reference to y is needed as an output but the extracted function uses y itself as the input to the function. This means that the ownership of y is passed to the function and so it cannot return a reference to the value that it owns.

Lifetime bounds

pub fn original_foo(){
    let p : &mut &i32 = &mut &0; // 'a
    {
        let x = 1;
        *p = &x; // 'b
    }
}

Rust Analyzer again did not account for lifetimes, and while it did highlights the error to the user, it passes ownership of x to the extracted function rather than an immutable reference:

pub fn new_foo(){
    let p : &mut &i32 = &mut &0;
    {
        let x = 1;
        bar_extracted(p, x);
    }
}

fn bar_extracted(p: &mut &i32, x: i32) {
    *p = &x;
}

Extract to trait

trait MultiLifetimeTrait<'b, 'a: 'b> {
    fn trait_function(self: &Self, x: & 'a i32, y: &'b i32) -> & 'b i32;
}

struct SimpleStruct;

impl<'b, 'a: 'b> MultiLifetimeTrait<'b, 'a> for SimpleStruct {
    fn trait_function(&self, x: &'a i32, y: &'b i32) -> &'b i32 {
        if *x < *y {
            y
        } else {
            x
        }
    }
}

pub fn original_foo<'b, 'a: 'b>(x: &'a i32, y: &'b i32) {
    let foo = SimpleStruct;
    let z = &mut &0;
    *z = foo.trait_function(x, y);
}

Similarly, no lifetime annotation here but errors reported.

pub fn new_foo<'b, 'a: 'b>(x: &'a i32, y: &'b i32) {
    let foo = SimpleStruct;
    let z = &mut &0;
    *z = bar_extracted(foo, x, y);
}

fn bar_extracted(foo: SimpleStruct, x: &i32, y: &i32) -> &i32 {
    foo.trait_function(x, y)
}

Multiple expressions with different lifetimes

pub fn original_foo1<'a> (p: &'a mut &'a i32, x: &'a i32){
    *p = x;
}

pub fn original_foo2<'a, 'b : 'a>(p: &'a mut &'b i32, x: &'b i32){
    *p = x;
}

Again, no lifetime annotations but errors reported. Rust Analyzer did not try to replace any other "duplicate" code:

pub fn new_foo1_fixed<'a> (p: &'a mut &'a i32, x: &'a i32){
    bar_fixed(p, x);
}

fn bar_fixed<'a>(p: &'a mut &'a i32, x: &'a i32) {
    *p = x;
}

Clone this wiki locally