Guess
Systems in Rust
Announcements
- Lab Day
- Prepare for Wordle
Homework
- Wordle is ready after this class.
- It is exactly hard enough to use all programming basics.
- Windows development is no longer supported.
- Due Friday, 19 Sept. at 1440 ET.
Citation
- The idea to do Wordle as the first Rust assignment was inspired by the Chapter 2 of the Rust Book, Guessing Game
- The idea to do Wordle as a programming assignment is inspired by the Nifty Assignment of the same name.
Today
consts
Compile time
- In Wordle, you’ll likely have two kinds of values that won’t change.
- The “ANSI Escape Codes” which allow you to print to the terminal in color, which are set when you install your operating system.
- For these we may use a Rust
const
- For these we may use a Rust
- The “answer” which will be set as soon as the program begins running, and never be altered thereafter.
- For these, we use a
let
.
- For these, we use a
- The “ANSI Escape Codes” which allow you to print to the terminal in color, which are set when you install your operating system.
- I refer to the first as “compile time constants” and declare them outside of any function.
src/main.rs
This section transparently added in response to a question in class. I do not use const
in my own code rather than let
, though perhaps I should.
Create constants
- Create your own constants for yellow and green.
- You may want to write a loop to try different colors, or consult documentation.
- For Wordle, I used a function with the following type, which you may wish to take as inspiration, to print a single character in a given color.
Character iteration
Strings lack indices
- In Python, we have some packing and unpacking to do to edit strings by character index.
Rust uses .chars()
- In the case of Wordle, all solutions are formulated as 5 characters, so we can assume the underlying character-ness of strings.
- This is not permitted generally in Rust, but in our case we may.
- To unpack Rust strings into characters, you may use
.chars()
- What do you see?
- Can you access the
i
th element? - Can you loop over object?
- Can you access the
Iterators
- What
.chars()
returns is called an iterator, it is much like a Python generator.
>>> gen = (i ** i for i in range(100))
>>> next(gen)
1
>>> next(gen)
1
>>> next(gen)
4
>>> next(gen)
27
>>> next(gen)
256
- Basically, it is a collection type of unknown size where next elements may be queried.
Rust uses .nth()
- You can see the
n
th element of an iterator with.nth()
, more or less:
- What do you see?
- What happens if you ask for the 256th letter?
Options
- Rust library functions almost always return an
Option
- This is just good practice, it beats returning e.g. Python
None
sometimes, or crashing.
- This is just good practice, it beats returning e.g. Python
- An option is simple:
Options for options
Pattern match
- The language designers intend options to be handled as follows:
src/main.rs
- I very rarely see code that looks like this, including in official Rust documentation.
- For example, the Polars documentation does not manage
Option
return types this way. - This is, however, the only way to ensure code does not crash on e.g. arbitrary length input.
Expect
- The incrementally less heavyweight option is with
.expect()
, a method of options that either:- Sucessful unpacks the option into a usable type, or
- Causes a “panic” - a comparatively graceful program crash.
- This panic prints the message you furnished to expect.
src/main.rs
- Try checking for existing and non-existant characters. What do you find?
Rust uses .unwrap()
- The incrementally less heavyweight option is with
.unwrap()
. - Unwrap does not require a message but is otherwise just like
.expect()
- Unwrap is used by e.g. the Polars documentation.
- Try
.unwrap()
on both successful and failed.nth()
calls.
Unwrap vs. Expect
- I have never voluntarily used
.expect()
instead of.unwrap()
, but…
If your code uses .unwrap()
instead of .expect()
you should carefully consider converting any .unwrap()
s to .expect()
s before asking someone else, who is less familiar with the assumptions you made when writing your code, for help.
This applies to colleagues, QUAD TAs, and potential to the course instructor depending on how busy things are.
/dev/random
Randomization
- There are always many ways to generated results that are vaguely random.
- As a security researcher, I am required to instruct you about
/dev/random
rather than use the Rust Book recommendations. - Separately we:
- Learn to read from a file.
- Get to think about how random different things really are.
Quoth Wikipedia
Peep it
- Verify you are on a system implementing
/dev/random
with the following, at command line:
- For me, I see:
$ head -1 /dev/random
6��c�W�Y|S�t��|��=���U�>�$��8�����E���*�&;�F�§�6␦8�{X�~�1U�J␦�Y���Eyg��Z��{ ��^"/%!7���vv@�w{p��q�y"� AD�/Ahb��fb��Ed�
�k�:���F�>����09h��Ʊl#�>�J����:J����5|I�E���04�������NH��-�X����l�,k�<�������=.4^qav�}Y��
��(�)1���B���c)�&*�#r��"H�(�:�e���֩A
$
- You can test what
head
does on a file with which you are familiar, or consultman head
File I/O
open
- A la Python, Rust utilizes
open
to read files. - Unlike Python, Rust has a great love for gobs of a text and
Option
s. - The following opens
/dev/random
to be read.- Think about why we need to
.unwrap()
when opening a file. - What is the Python equivalent?
- Think about why we need to
- We require mutability to be able to read successive bits from the file, as our location in the file is tracked within the Rust
File
object.
An object providing access to an open file on the filesystem.
use
- A lot of people who aren’t me prefer to use
use
to have shorter names. - I don’t personally understand this, but I do use
from pgl import *
in Python. - Here’s an example from Rust documentation of a
use
:
read
- True to form,
read
has a variety of complexities introduced by:- Not assuming anything about the Rust
File
object - Not assuming anything about how to read or save data.
- Not assuming anything about the underlying file within the computer’s file system.
- Not assuming anything about the Rust
- I use it as follows:
- An astute reader will notice a few idiosyncracies.
&mut
- When we:
- Have a variable in Rust
- For which the ownership model applies, such as a file of arbitrary size
- For which mutability is necessary, such as a
File
object from whichn
bytes have been read.
- We can use this variable:
- Within some other function, while
- Retaining the ability to use it again in some future function.
- We do so by passing a “mutable reference”, generated by prefixing the variable name with
&mut
and a space.
Buffers
- It is common in lower level languages to read from a file into a “buffer”, a temporary storage space within the executing program.
- These are commonly implemented as multiple of bytes of some fixed size in that language’s array type.
- These bits are commonly initialized to zero.
- I am aware of no graceful way to do this in Rust, so I’ll tell you what I’m doing now and why.
Arrays
- Arrays in Rust are fixed size and typed, not unlike NumPy array.
- They see little use versus the more common vector type, but I preferred arrays for Wordle.
- And in fact expect to prefer arrays this term.
Quoth the Docs
A fixed-size array, denoted
[T; N]
, for the element type,T
, and the non-negative compile-time constant size,N
.There are two syntactic forms for creating an array:
A list with each element, i.e.,
[x, y, z]
.A repeat expression
[expr; N]
whereN
is how many times to repeat expr in the array. expr must either be:- A value of a type implementing the Copy trait
- A const value
I never read this, I found this page through a search engine and ripped it.
- Search for
dev/urandom
on that page.
- Search for
Create an array
- The following is a mutable - so we can read file data into it - array of 8 bytes.
- Breakdown.
- The
0
in0u8
is the initialization value. - The
u8
is the type, unsigned 8 bit value. - The post comma
8
is the number of 8 bit values to store.
- The
- There’s only one problem here.
- That second
8
is a magic number, which to me represents poor style.- Believe me, I wanted to just type 8, but we shouldn’t.
- That second
On Magic Numbers
- To select a random word, eventually you will probably have:
- An array of 5 letter words.
- That array will have some length.
- That length will be whatever Rust uses to store the size of memory objects.
- Different computers have different ways of address memory…
- So we cannot make assumptions about the size of values which themselves store the size of memory.
- I wish I was kidding! I’m not!
32 vs 64 bit
- For example, how many memory locations can be addressed by a 32 bit system?
- How many on a 64 bit system?
- Does your device potentially use 48 bit addresses?
- How could you tell?
- What if you run code on a 16 bit microcontroller?
- Enter
usize
- The unsigned value that is the right size to store a size.
usize
- We will use
usize
by name twice:- We need to read a random usize.
- We can determine how many
u8
s are required to make up ausize
by checking how many bits are in ausize
. and dividing by 8.
- Only one problem - for some reason
usize
stores its size as au32
. - So to get enough bits to fill I
usize
, I ended up doing… this?
Presumably I’m doing this wrong.
Check in
- I have this so far:
Candidate Answers
- You can imagine testing with a smaller word list, like the words of the Sator Square
- Here
&str
is used to refer to string literals, that is, not theString
quasi-data structure of arbitrary size.- This was the type that e.g.
"hello world"
had the whole time under the hood.
- This was the type that e.g.
src/main.rs
- This doesn’t work.
Indices
- An array can be as large as memory.
- So it’s index can be as large as memory.
- The thing as large memory is
usize
.
Byte array to usize
- Usize helpfully has “from bytes” methods.
- There are 3 such methods for different endiannesses, a future topic.
- I use little endianness because it doesn’t matter -
le
- This can be used as an index, but it’s probably too large.
.len()
- Rust furnishes the
.len()
method of arrays to detect their type.
Altogether
- We use
String::from()
to get aString
from a&str
, as the array contains fixed length strings and I use theString
type for consistency with lecture recommendations.
Today
stdin
input()
- Python furnishes the straight-forward
input()
function. - We don’t have that in rust, but we do have”
stdin()
- the “standard input” that represents text provided at terminal..read_line()
- like Python.read_line()
in that it reads to the next newline character, but a bit different in type.
- Essentially,
stdin()
is a function that returns aFile
the refers to the terminal.
- What happens if you don’t unwrap?
- What type is
guess
? Array?&str
?String
?
.trim()
- Reading lines will return slightly longer strings that end with a newline character.
- This can be removed with
.trim()
. - Try reading in e.g.
opera
ortenet
and seeing the trimmed vs. untrimmed versions.- You may want to inspect their length, perhaps by converting to characters and using
.nth()
.
- You may want to inspect their length, perhaps by converting to characters and using
.contains()
- You can check if a value is present in an array using
.contains()
. - Check to see if the trimmed and untrimmed versions of
opera
ortenet
are contained in theWORDS
array.- You will need to fiddle with types a bit!
- Will you need to use
&
?&mut
?String::from()
? Try each and see what happens - and consider why!
.clone()
- Sometimes you will need to read a word from
stdin
and perhaps both compare it some other word and also decompose it into characters. - If you need multiple copies, you may want to use
.clone()
orcargo
may recommend use of.clone()
.
Closing Thoughts
range()
- You may benefit from using
for
loops of fixed index. - Rust has a syntactical rather than functional range.
- It follows the same start/stop rules as Python.
Vectors
- A lot of Rust programmers are extremely fond of the Vector, which completely coincidentally was not necessary for Wordle as everything in Wordle is fixed size.
- If you want to learn Vectors instead of arrays, simply consult this documentation.
- [Rust By Example: Vectors]
- You will find them similar to Python
list
.