Skip to main content

Rust Lang Endeavour

rust-lang

my git repo

Im working / learning on Windows 10 using CLion as well as MacOS - which is a lot handier for commandline tasks ;-)

Rust / Cargo Installation

  1. Download rust and follow the commandline instructions.
  2. Install the gnu toolchain
  • rustup install stable-gnu
  1. Set the gnu toolchain as your default
  • rustup set default-host x86_64-pc-windows-gnu

Debugging rust on Windows

The easiest solution for a working debugging environment on windows, is to install and use cygwin.

Download the main installation .exe and during the wizard, select and download/install at least the following four packages:

If you are using CLion, your settings should look similar to this: clion_cygwin_toolchain Debugging works regardless of the unsupported version warning.

To create this toolchain, click on the plus icon and select cygwin. If you have installed cygwin in its default location, CLion will autodetect all necessary settings.

Useful Commands

  1. cargo run
  2. cargo build
  3. cargo build --release

Basic Syntax

Functions and import

The main() function is rusts application entry point. mod imports the file loops.rs. loops::run() calls the run() method from the loops.rs file.

// main.rs

mod loops;

fn main() {
loops::run();
}
// loops.rs

pub fn run() {
let mut count = 0;

// print until count equals 10, then break.
loop {
count += 1;
println!("{}", count);

if count == 10 {
break;
}
}
}

Basic print outs

//    print to console
println!("We are using curly braces to print variables / values other than strings. Escape curly braces via double curly brace.");

// using {} as format placeholders similar to java LOG.xx
println!("{} is from {}", "luca", "hamburg");

// we can also use numbers to refer to specific given values
println!("{0} is from {1}", "luca", "hamburg");

// we can also name the placeholder
println!("{name} is from {city}", name = "luca", city = "hamburg");

// we can also parse the incoming variable to binary, hex or octal e.g
println!("Binary: {:b}, Hex: {:x} Octal: {:o}", 1000, 1000, 1000);
// Binary: 1111101000, Hex: 3e8 Octal: 1750

Basic var usage

//    vars are always immutable.
let name = "Luca";
// type inferred to &str -> string

let immutable = "immutable";
let mut growable = String::from("groable");

// adding `mut` -> makes it mutable.
let mut age = 25;
// type inferred to i32 -> 32bit signed integer

println!("Name: {}", name);
println!("Age is: {}", age);

age += 1;
println!("And now the age is: {}", age);

const ID: i32 = 001;
// type manually set to i32 -> 32bit signed integer

// Tuples
let person: (&str, &str, i8) = ("firstname", "lastname", 25);
println!("{} {} {}", person.0, person.1, person.2);

println!("Printing multiple vars using the debug placeholder :? - {:?}", (name, age));

// Conditionals
let height = 10;

if height >= 10 {
println!("Height: {} is ok. You can pass.", height);
}

Basic types

//    default type -> i32
let integer = 1;

// default type -> f64
let float = 1.1;

// manually set type -> i64
let integer_large: i64 = 1233456789;

// Boolean
let is_alive = true;

println!("Printing multiple vars using the debug placeholder :? - {:?}", (integer, float, is_alive));

// Char
let emoji = '\u{1F600}';
println!("We can even print unicode emojis: {:?}", emoji);
// '😀'

// find max sizes - signed
println!("Max i8: {}", std::i8::MAX);
// 127
println!("Max i16: {}", std::i16::MAX);
// 32767
println!("Max i32: {}", std::i32::MAX);
// 2147483647
println!("Max i64: {}", std::i64::MAX);
// 9223372036854775807
println!("Max i128: {}", std::i128::MAX);
// 170141183460469231731687303715884105727

Unsigned integer types cannot hold negative values.

//    find max sizes - unsigned
println!("Max u8: {}", std::u8::MAX);
// 255
println!("Max u16: {}", std::u16::MAX);
// 65535
println!("Max u32: {}", std::u32::MAX);
// 4294967295
println!("Max u64: {}", std::u64::MAX);
// 18446744073709551615
println!("Max u128: {}", std::u128::MAX);
// 340282366920938463463374607431768211455

Arrays

//    array of exactly 5 i32.
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
println!("{:?}", numbers)

// get size in memory. Pass reference via '&'.
// i32 -> 4 bytes. *5 arrays size -> 20bytes.
println!("Size in mem: {} bytes.", std::mem::size_of_val(&numbers));
// Size in mem: 20 bytes.

// slice array
let slice_part: &[i32] = &numbers[0..2];
println!("Slice: {:?}", slice_part);
// Slice: [1, 2]

Vectors

    let mut numbers: Vec<i32> = vec![1, 2, 3, 4, 5];

// reassign value
numbers[1] = 99;

// add on to vector
numbers.push(6);
numbers.push(7);

// remove last entry from vector
numbers.pop();

// print entries in for loop via iterator.
for number in numbers.iter() {
println!("Entry: {}", number);
}

// loop and multiply values by 2.
for entry in numbers.iter_mut() {
*entry *= 2;
}
println!("Entry: {:?}", numbers);
// Entry: [2, 198, 6, 8, 10, 12]

Loops

    let mut count = 0;

// print until count equals 10, then break.
loop {
count += 1;
println!("{}", count);

if count == 10 {
break;
}
}

SDL Installation Windows/Mac

Your Cargo.toml should look as follows

[package]
name = "opengl-test"
version = "0.1.0"
authors = ["Firstname Lastname <name@domain.com>"]
build = "build.rs"

[dependencies]
sdl2 = "0.31.0"
gl = "0.11.0"

Noticed the line build = "build.rs"? This is a rust build script. In our case, we use it to load windows specific stuff.

MacOS - Homebrew

As almost always, its easier to use a Unix OS ;-)

Use/install Homebrew and run the following command. You should be good to compile.

brew install sdl2

Windows 10

The following excerpt is copied from the rust-sdl2 repository. You need to download archives for mingw1 and vc2. Extract these, and copy specific files inside corresponding folder on the same level as your Cargo.toml.

SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\i686-w64-mingw32\bin 		-> 	gnu-mingw\dll\32
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\x86_64-w64-mingw32\bin -> gnu-mingw\dll\64
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\i686-w64-mingw32\lib -> gnu-mingw\lib\32
SDL2-devel-2.0.x-mingw.tar.gz\SDL2-2.0.x\x86_64-w64-mingw32\lib -> gnu-mingw\lib\64
SDL2-devel-2.0.8-VC.zip\SDL2-2.0.x\lib\x86\*.dll -> msvc\dll\32
SDL2-devel-2.0.8-VC.zip\SDL2-2.0.x\lib\x64\*.dll -> msvc\dll\64
SDL2-devel-2.0.8-VC.zip\SDL2-2.0.x\lib\x86\*.lib -> msvc\lib\32
SDL2-devel-2.0.8-VC.zip\SDL2-2.0.x\lib\x64\*.lib -> msvc\lib\64

Your folder structure should look like this:

buildrs_folder_structure


Create your build.rs file with the following content:

use std::env;
use std::path::PathBuf;

fn main() {
let target = env::var("TARGET").unwrap();
if target.contains("pc-windows") {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let mut lib_dir = manifest_dir.clone();
let mut dll_dir = manifest_dir.clone();
if target.contains("msvc") {
lib_dir.push("msvc");
dll_dir.push("msvc");
}
else {
lib_dir.push("gnu-mingw");
dll_dir.push("gnu-mingw");
}
lib_dir.push("lib");
dll_dir.push("dll");
if target.contains("x86_64") {
lib_dir.push("64");
dll_dir.push("64");
}
else {
lib_dir.push("32");
dll_dir.push("32");
}
println!("cargo:rustc-link-search=all={}", lib_dir.display());
for entry in std::fs::read_dir(dll_dir).expect("Can't read DLL dir") {
let entry_path = entry.expect("Invalid fs entry").path();
let file_name_result = entry_path.file_name();
let mut new_file_path = manifest_dir.clone();
if let Some(file_name) = file_name_result {
let file_name = file_name.to_str().unwrap();
if file_name.ends_with(".dll") {
new_file_path.push(file_name);
std::fs::copy(&entry_path, new_file_path.as_path()).expect("Can't copy from DLL dir");
}
}
}
}
}

This will create and copy the needed .dll files next to your Cargo.toml file during build time. Copy these to the directory where your .exe file might be placed in the future.

Executing cargo run should now successfully compile and run your program.

Initializing SDL and creating a Window

Simple DirectMedia Layer

[...] a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D [...]

As nercury described in his post, we need to instantiate the sdl context and use it to create a video subsystem with which we can open a window.

// main.rs

let sdl_context: Sdl = sdl2::init().unwrap();

// initialize video_subsystem
let video_subsystem: VideoSubsystem = sdl_context.video().unwrap();

// lets use it and create a window
let window: Window = video_subsystem
.window("Window", 800, 600)
.opengl()
.resizable()
.build()
.unwrap();

let gl_context: GLContext = window.gl_create_context().unwrap();

// load gl to forward our opengl calls to the driver.
let gl = gl::load_with(|s| video_subsystem.gl_get_proc_address(s) as *const std::os::raw::c_void);

unsafe {
// set color to blue
gl::ClearColor(0.3, 0.3, 0.5, 1.0);
}

We will then open a potentially infinite loop which will handle all user input and window content.

//main.rs

// create an EventPump to receive Events for n windows.
let mut event_pump: EventPump = sdl_context.event_pump().unwrap();

// label for this loop. Allows us to break free from inside the for loop.
'main: loop {
// an underscore as a variable prefix silences compiler warnings
for event in event_pump.poll_iter() {
// handle quit event
match event {
// pattern match for event 'Quit'. Allows the window to be closed.
sdl2::event::Event::Quit { .. } => break 'main,
_ => {}
}
// handle user input here.
}
unsafe {
// update buffer/screen
gl::Clear(gl::COLOR_BUFFER_BIT);
}

window.gl_swap_window();
}

Ive also create a small util.rs file and placed the above find_sdl_gl_driver function in it.

extern crate sdl2;

/// Find and return the OpenGL driver from SDL.
pub fn find_sdl_gl_driver() -> Option<u32> {
for (index, item) in sdl2::render::drivers().enumerate() {
if item.name == "opengl" {
return Some(index as u32);
}
}
None
}

Find the complete files here:


If implemented correctly, running our app will result in an empty, resizable and closable window.

empty_opengl_window

Tutorials / Downloads

Footnotes

Footnotes

  1. sdl2_mingw.tar.gz ~ 9.8mb

  2. sdl2_vc.zip ~ 1.8mb