Skip to content

Commit

Permalink
Adding notebook
Browse files Browse the repository at this point in the history
  • Loading branch information
rarebreed committed Mar 10, 2023
1 parent 0038e79 commit 9531bba
Show file tree
Hide file tree
Showing 6 changed files with 376 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[workspace]

members = [
"protean"
"protean",
"guro"
]

exclude = [
Expand Down
8 changes: 8 additions & 0 deletions guro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "guro"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
9 changes: 9 additions & 0 deletions guro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# guro

guro is a teaching project for rust

It is a combination of sample code projects and a ipython notebook (requires evcxr_jupyter)

## What's with the name

guro is Tagalog for "teacher"
350 changes: 350 additions & 0 deletions guro/rust-memory.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"It's important to understand the stack and the heap when working with rust, and the earlier you understand this concept the better.\n",
"\n",
"Many modern languages do not make a distinction between the stack and heap, so this may be a new concept. The stack is a region that the Operating System uses to execute instructions of your program. When you define a function, room has to be made on the stack for the arguments, and when the function exits, all the previously allocated elements on the stack are popped off and the return value is pushed onto the stack.\n",
"\n",
"This is why recursive functions can `stack overflow`, because on each new function call, room for the arguments of the function are pushed onto the stack, and if this happens too much before the recursion stops, the stack space can be fully consumed (because the Operating System has a limit on how big the stack space is, and this is determined by the kernel when it is built usually)\n",
"\n",
"So let's see how this works."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Address of a: 0x7ffcbe3e6d30\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Size of a is 4\n",
"Address of b: 0x7ffcbe3e6d34\n",
"Size of b is 4\n",
"Address of c: 0x7ffcbe3e6d38\n",
"Size of c is 16\n",
"Address of d: 0x7ffcbe3e6d28\n",
"Size of d is 8\n"
]
}
],
"source": [
"use std::mem::{size_of, size_of_val};\n",
"\n",
"fn printa<A>(x: &A, val: &str) {\n",
" println!(\"Address of {val}: {:p}\", x);\n",
"}\n",
"\n",
"fn stack_print() {\n",
" let a = 1;\n",
" let b = 2;\n",
" let c = \"hi\";\n",
" let d = Box::new(10);\n",
" \n",
" printa(&a, \"a\");\n",
" println!(\"Size of a is {}\", size_of_val(&a));\n",
" printa(&b, \"b\");\n",
" println!(\"Size of b is {}\", size_of_val(&b));\n",
" printa(&c, \"c\");\n",
" println!(\"Size of c is {}\", size_of_val(&c));\n",
" printa(&d, \"d\");\n",
" println!(\"Size of d is {}\", size_of_val(&d));\n",
"}\n",
"\n",
"stack_print();"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Address of a1: 0x7ffcbe3e6cc8\n",
"Address of a2: 0x7ffcbe3e6cc8\n"
]
}
],
"source": [
"struct Foo {}\n",
"\n",
"let a1 = Foo {};\n",
"let a2 = Foo {};\n",
"\n",
"printa(&a1, \"a1\");\n",
"printa(&a2, \"a2\");"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice that both `a1` and `a2` point to the same address. Also note we had to take a reference to these two values to be able to use the `:p` formatting in `println!`.\n",
"\n",
"But why do they both point to the same address? Didn't we create 2 new variables of type `Foo`? Shouldn't we see two different addresses?\n",
"\n",
"The answer is that unlike most garbage collected languages that create instances of data on the heap, `Foo {}` is just a value, no different 10, but, Foo also has no data associated with it. What if we made a struct with data?"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Address of x: 0x7ffcbe3e6cb8\n",
"Address of y: 0x7ffcbe3e6cbc\n"
]
}
],
"source": [
"#[derive(Debug)]\n",
"struct Bar { x: u32 }\n",
"\n",
"let x = Bar { x : 10 };\n",
"let y = Bar { x: 20 };\n",
"printa(&x, \"x\");\n",
"printa(&y, \"y\");"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice that now, we have different addresses for each. This is because rust has to allocate memory for this data type unlike with Foo.\n",
"\n",
"So far all these variables have lived on the stack. What if we have a Box or Rc? An important distinction must be made between the adress of a variable on the stack, with what it points to. We will illustrate this with the following:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Address of z: 0x7ffcbe3e6cd0\n",
"Address in memory of z is 0x5a8536a90ab0\n"
]
}
],
"source": [
"let z = Box::new(y);\n",
"printa(&z, \"z\");\n",
"let raw: *mut Bar = Box::into_raw(z);\n",
"println!(\"Address in memory of z is {raw:p}\");"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Why did `z` print one address, but `raw` pointed to a different value?\n",
"\n",
"In rust, we must differentiate between:\n",
"\n",
"1. the name of the symbol, eg `z`\n",
"2. the value of the symbol\n",
"3. where the data that symbol refers to lives\n",
"\n",
"Unfortunately, all 3 of these get easily confused, especially when we start talking about references or pointers (and a reference is basically a safe pointer). When data just lives on the stack, 1 and 2 are typically a simple mapping. For example with \n",
"\n",
"```\n",
"let x = 10;\n",
"```\n",
"\n",
"then we can think of x **as** 10. But technically, `x` is the name of the symbol, which points to some memory on the stack, and the value of 10. When we run the `printa` function, we are showing the address on the stack."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The address on the stack of x is 0x7ffcc9f1bab4 and its value is 10\n"
]
}
],
"source": [
"let x = 10;\n",
"println!(\"The address on the stack of x is {:p} and its value is {}\", &x, *&x);"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"This explains why `z` (the symbol name that refers to some address on the stack) has a different address than the actual value of `z`. This is because point of #3. Since `z` value is actually a smart pointer, where the data that `z` refers to is different."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The address on the stack of z is 0x7ffcbe3e6ca8 and its value is\n",
"Bar {\n",
" x: 30,\n",
"}\n",
"The address of what z points to is 0x5a8536a92250 and its value is\n",
"Bar {\n",
" x: 30,\n",
"}\n"
]
},
{
"data": {
"text/plain": [
"()"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"let q = Bar { x: 30 };\n",
"let z = Box::new(q);\n",
"println!(\"The address on the stack of z is {:p} and its value is\\n{:#?}\", &z, *&z);\n",
"let raw = Box::into_raw(z);\n",
"unsafe {\n",
" println!(\"The address of what z points to is {:p} and its value is\\n{:#?}\", raw, *raw);\n",
"}"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that we have a basic understanding of the difference between the stack, let's look at how rust deals with &str and String.\n",
"\n",
"So first off, why two different kinds of strings? In rust, performance is important for systems programming so having control over stack or heap allocated data is important. But more importantly, some data types we can't know at compile time, because they may grow in size. For example, a Vec in rust is a type that can hold values of another but grow dynamically. A String in rust, is a dynamically resizeable type that happens to hold u8 values, that happen to be UTF8 encoded."
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Address of name: 0x7ffcbe3e6d00\n",
"The address of what name points to is 0x5a8536a94500\n",
"Address of sean: 0x7ffcbe3e6cd8\n",
"Size of sean is 4\n",
"Size of full is 10\n",
"Size of full is 10\n"
]
}
],
"source": [
"// name has type String, and name itself lives on the stack\n",
"// however, the data of name lives on the heap. Notice how this is similiar to Box\n",
"let name = String::from(\"Sean\");\n",
"\n",
"printa(&name, \"name\");\n",
"let name_box = name.into_boxed_str();\n",
"let data_addr_of_name = Box::into_raw(name_box);\n",
"unsafe {\n",
" println!(\"The address of what name points to is {:p}\", data_addr_of_name);\n",
"}\n",
"\n",
"let sean = \"Sean\";\n",
"printa(&sean, \"sean\");\n",
"let str_size = std::mem::size_of_val(sean);\n",
"println!(\"Size of sean is {str_size}\");\n",
"\n",
"let full = \"sean toner\";\n",
"let full_size = std::mem::size_of_val(full);\n",
"println!(\"Size of full is {full_size}\");\n",
"let full_len = full.len();\n",
"println!(\"Size of full is {full_len}\");\n",
"\n",
"let name = String::from(\"Sean Toner\");\n",
"println(\"size of name is {}\", )\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Rust",
"language": "rust",
"name": "rust"
},
"language_info": {
"codemirror_mode": "rust",
"file_extension": ".rs",
"mimetype": "text/rust",
"name": "Rust",
"pygment_lexer": "rust",
"version": ""
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
3 changes: 3 additions & 0 deletions guro/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

0 comments on commit 9531bba

Please sign in to comment.