Home > Software engineering >  How do I tell the borrow checker this is safe?
How do I tell the borrow checker this is safe?

Time:07-26

use image::{Rgb, RgbImage};
use rayon::prelude::*;

#[inline]
fn lerp(pct: f32, a: f32, b: f32) -> f32 {
    pct.mul_add(b - a, a)
}

#[inline]
fn distance(x: i32, y: i32) -> f32 {
    ((x * x   y * y) as f32).sqrt()
}

struct ColorCalculator {
    from: [f32; 3],
    to: [f32; 3],
    center_x: i32,
    center_y: i32,
    max_dist: f32,
}

impl ColorCalculator {
    fn new(from: [u8; 3], to: [u8; 3], width: u32, height: u32) -> Self {
        let center_x = width as i32 / 2;
        let center_y = height as i32 / 2;
        Self {
            from: from.map(|channel| channel as f32),
            to: to.map(|channel| channel as f32),
            center_x,
            center_y,
            max_dist: distance(center_x, center_y),
        }
    }
    fn calculate(&self, x: u32, y: u32) -> [u8; 3] {
        let x_dist = self.center_x - x as i32;
        let y_dist = self.center_y - y as i32;

        let t = distance(x_dist, y_dist) / self.max_dist;

        [
            lerp(t, self.from[0], self.to[0]) as u8,
            lerp(t, self.from[1], self.to[1]) as u8,
            lerp(t, self.from[2], self.to[2]) as u8,
        ]
    }
}

fn radial_gradient(geometry: [u32; 2], inner_color: [u8; 3], outer_color: [u8; 3]) -> RgbImage {
    let [width, height] = geometry;
    let color_calculator = ColorCalculator::new(inner_color, outer_color, width, height);

    let mut background = RgbImage::new(width, height);

    (0..height / 2).into_par_iter().for_each(|y| {
        for x in 0..width / 2 {
            let color = Rgb(color_calculator.calculate(x, y));
            background.put_pixel(x, y, color);
            background.put_pixel(width - x - 1, y, color);
            background.put_pixel(x, height - y - 1, color);
            background.put_pixel(width - x - 1, height - y - 1, color);
        };
    });
    
    background
}

I know that I could just use a mutex here although it is unnecessary since provided my code is correct no pixel should be mutated more than once. So how do I tell rust that doing background.put_pixel(x, y, color) in multiple threads is actually okay here?

I'm guessing some use of unsafe has to be used here although I am new to rust and am not sure how to use it effectively here.

Here's the error

error[E0596]: cannot borrow `background` as mutable, as it is a captured variable in a `Fn` closure
   --> src\lib.rs:212:13
    |
212 |             background.put_pixel(x, y, color);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

error[E0596]: cannot borrow `background` as mutable, as it is a captured variable in a `Fn` closure
   --> src\lib.rs:213:13
    |
213 |             background.put_pixel(width - x - 1, y, color);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

error[E0596]: cannot borrow `background` as mutable, as it is a captured variable in a `Fn` closure
   --> src\lib.rs:214:13
    |
214 |             background.put_pixel(x, height - y - 1, color);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

error[E0596]: cannot borrow `background` as mutable, as it is a captured variable in a `Fn` closure
   --> src\lib.rs:215:13
    |
215 |             background.put_pixel(width - x - 1, height - y - 1, color);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

CodePudding user response:

You can't. At least not with an RgbImage.

put_pixel takes a &mut self. In Rust, it's undefined behavior to have two &mut references alias - the optimizer can do some funky stuff to your code if you break this assumption.

You will probably have an easier time creating a Vec<u8> of pixel data, calculating each pixel's value using Rayon's parallel iterators (which will take special care to not alias the mutable references), then assemble the buffer into an image using from_vec.

CodePudding user response:

You can use a raw pointer, cast it to unsigned integer, and then unsafely dereference inside the loop:

let background: *mut RgbImage =
    &mut RgbImage::new(width,height) as *mut RgbImage;
let pointer = background as usize;
(0..height / 2).into_par_iter().for_each(|y| {
    for x in 0..width / 2 {
        ...
        let mut background = unsafe { (pointer as *mut RgbImage).as_mut() }.unwrap();
        ...
    };
});

unsafe { background.as_ref().unwrap() })
  • Related