5
\$\begingroup\$

As my go to first project for a language is a Mandelbrot, I had to implement one in Rust. I'm particularly annoyed by invalid_arg_count, as I feel like there has to be a way to add the arg length checking to the match. I also wish to find a way to multi-thread this in the future.

extern crate image;
extern crate num;

use std::env;
use num::Complex;

fn mandelbrot_at(c: Complex<f64>) -> bool {
    const ITERATIONS: u32 = 1000;

    let mut z: Complex<f64> = c.clone();
    for _ in 0..ITERATIONS {
        if z.norm_sqr() > 4.0 {
            return false;
        }
        z = z * z + c;
    }

    true
}

fn mandelbrot_at_pixel(x: u32, y: u32, img_width: u32, img_height: u32) -> image::Rgb<u8> {
    const MIN_X: f64 = -2.5;
    const MAX_X: f64 = 1.0;
    const MIN_Y: f64 = -1.0;
    const MAX_Y: f64 = 1.0;

    let x = (x as f64) / (img_width as f64) * (MAX_X - MIN_X) + MIN_X;
    let y = (y as f64) / (img_height as f64) * (MAX_Y - MIN_Y) + MIN_Y;

    if mandelbrot_at(Complex::new(x, y)) {
        image::Rgb { data: [0xB7, 0x41, 0x0E] } // aka "rust"
    } else {
        image::Rgb { data: [0, 0, 0] }
    }
}

fn mandelbrot(width: u32, height: u32) -> image::RgbImage {
    image::RgbImage::from_fn(width,
                             height,
                             |x, y| mandelbrot_at_pixel(x, y, width, height))
}

fn invalid_arg_count() -> ! {
    panic!("Must have exactly 3 arguments: width, height, destination (file)");
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let args = match args.split_first() {
        Some((_, slice)) => slice,
        None => invalid_arg_count(),
    };

    if args.len() != 3 {
        invalid_arg_count();
    }

    let width: u32 = args[0]
        .parse()
        .expect(&format!("Width must be an integer, was `{}`", args[0]));
    let height: u32 = args[1]
        .parse()
        .expect(&format!("Height must be an integer, was `{}`", args[1]));
    let ref dest = args[2];

    println!("Generating mandelbrot of size {} by {}.", width, height);

    let mandelbrot = mandelbrot(width, height);

    println!("Writing mandelbrot to {}", dest);

    mandelbrot.save(dest).unwrap();
}

This has dependencies on image and num.

Example usage:

mandelbrot 200 100 test.png

Output:

mandelbrot

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

The code was very easy to read and it seems like you took your time to follow the normal coding style; kudos!

Some small nits:

  1. There's no need for an explicit clone() in mandelbrot_at as Complex implements Copy. There's also no need to explicitly state the type of z.
  2. Avoid comments like "aka rust" by naming constants instead.
  3. Use Iterator::skip to ignore the program name when reading the arguments.
  4. When collecting, it's common to let the compiler fill in the type of the item by using an underscore in the type, like Vec<_>.
  5. It's not common to use let ref foo. The ref keyword is mostly used in match or if let statements. It's more usual to add a & on the right hand side.
  6. Prefer expect over unwrap. This gives you a chance to talk to the user and it makes it easier for the programmer to pinpoint the failing line of code.
  7. It's a bit of a silly optimization, but the original code always formats a new string for the error messages, but it only displays it when a failure occurs. You can use unwrap_or_else paired with a panic! to avoid that.

extern crate image;
extern crate num;

use std::env;
use num::Complex;

fn mandelbrot_at(c: Complex<f64>) -> bool {
    const ITERATIONS: u32 = 1000;

    let mut z = c;
    for _ in 0..ITERATIONS {
        if z.norm_sqr() > 4.0 {
            return false;
        }
        z = z * z + c;
    }

    true
}

fn mandelbrot_at_pixel(x: u32, y: u32, img_width: u32, img_height: u32) -> image::Rgb<u8> {
    const MIN_X: f64 = -2.5;
    const MAX_X: f64 = 1.0;
    const MIN_Y: f64 = -1.0;
    const MAX_Y: f64 = 1.0;

    const COLOR_RUST: [u8; 3] = [0xB7, 0x41, 0x0E];
    const COLOR_BLACK: [u8; 3] = [0, 0, 0];

    let x = (x as f64) / (img_width as f64) * (MAX_X - MIN_X) + MIN_X;
    let y = (y as f64) / (img_height as f64) * (MAX_Y - MIN_Y) + MIN_Y;

    let color = if mandelbrot_at(Complex::new(x, y)) {
        COLOR_RUST 
    } else {
        COLOR_BLACK 
    };

    image::Rgb { data: color }
}

fn mandelbrot(width: u32, height: u32) -> image::RgbImage {
    image::RgbImage::from_fn(width,
                             height,
                             |x, y| mandelbrot_at_pixel(x, y, width, height))
}

fn main() {
    let args: Vec<_> = env::args().skip(1).collect();

    if args.len() != 3 {
        panic!("Must have exactly 3 arguments: width, height, destination (file)");
    }

    let width: u32 = args[0]
        .parse()
        .unwrap_or_else(|e| panic!("Width must be an integer, was `{}`: {}", args[0], e));
    let height: u32 = args[1]
        .parse()
        .unwrap_or_else(|e| panic!("Height must be an integer, was `{}`: {}", args[1], e));
    let dest = &args[2];

    println!("Generating mandelbrot of size {} by {}.", width, height);
    let mandelbrot = mandelbrot(width, height);

    println!("Writing mandelbrot to {}", dest);
    mandelbrot.save(dest).expect("Unable to write file");
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.