Skip to content

Commit 54d5726

Browse files
Merge branch 'mr/491-create-lab-for-module-lifetimes' into 'master'
Resolve "Create Lab for Module: Lifetimes" Closes #491 See merge request feng/training/material!569
2 parents 3a8b3c8 + 01e2399 commit 54d5726

5 files changed

Lines changed: 265 additions & 113 deletions

File tree

Lines changed: 9 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,17 @@
1-
============================
2-
Exercise: Protobuf Parsing
3-
============================
4-
5-
------------------------------
6-
Protobuf Parsing Explanation
7-
------------------------------
8-
9-
In this exercise, you will build a parser for the
10-
:url:`protobuf binary encoding <https://protobuf.dev/programming-guides/encoding/>`. Don't
11-
worry, it's simpler than it seems! This illustrates a common parsing
12-
pattern, passing slices of data. The underlying data itself is never
13-
copied.
14-
15-
Fully parsing a protobuf message requires knowing the types of the
16-
fields, indexed by their field numbers. That is typically provided in a
17-
:rust:`proto` file. In this exercise, we'll encode that information into
18-
:rust:`match` statements in functions that get called for each field.
19-
20-
We'll use the following proto:
21-
22-
.. code:: proto
23-
24-
message PhoneNumber {
25-
optional string number = 1;
26-
optional string type = 2;
27-
}
28-
29-
message Person {
30-
optional string name = 1;
31-
optional int32 id = 2;
32-
repeated PhoneNumber phones = 3;
33-
}
34-
35-
36-
------------------------------
37-
More Information on Protobuf
38-
------------------------------
39-
40-
* Messages
41-
42-
A proto message is encoded as a series of fields, one after the next.
43-
Each is implemented as a :dfn:`tag` followed by the value. The tag contains a
44-
field number (e.g., :rust:`2` for the :rust:`id` field of a :rust:`Person` message)
45-
and a wire type defining how the payload should be determined from the
46-
byte stream. These are combined into a single integer, as decoded in
47-
:rust:`unpack_tag` below.
48-
49-
* Varint
50-
51-
Integers, including the tag, are represented with a variable-length
52-
encoding called VARINT. Luckily, :rust:`parse_varint` is defined for you
53-
below.
54-
55-
* Wire Types
56-
57-
Proto defines several wire types, only two of which are used in this
58-
exercise.
59-
60-
* The :rust:`Varint` wire type contains a single varint, and is used to encode proto values of type :rust:`int32` such as :rust:`Person.id`.
61-
62-
* The :rust:`Len` wire type contains a length expressed as a varint, followed by a payload of that number of bytes. This is used to encode proto values of type :rust:`string` such as :rust:`Person.name`. It is also used to encode proto values containing sub-messages such as :rust:`Person.phones`, where the payload contains an encoding of the sub-message.
63-
64-
----------------
65-
Protobuf Types
66-
----------------
67-
68-
.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-types :end-before://ANCHOR-helpers :code:rust
69-
70-
------------------
71-
Protobuf Helpers
72-
------------------
73-
74-
.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-helpers :end-before://ANCHOR-parse_field_solution :code:rust
1+
=====
2+
Lab
3+
=====
754

765
------------------
77-
Protobuf Problem
6+
Lab Instructions
787
------------------
798

80-
The given code also defines callbacks to handle :rust:`Person` and
81-
:rust:`PhoneNumber` fields, and to parse a message into a series of calls to
82-
those callbacks.
83-
84-
What remains for you is to implement the :rust:`parse_field` function and
85-
the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`.
86-
87-
.. code:: rust
88-
89-
/// Parse a field, returning the remaining bytes
90-
fn parse_field(data: &[u8]) -> (Field, &[u8]) {
91-
let (tag, remainder) = parse_varint(data);
92-
let (field_num, wire_type) = unpack_tag(tag);
93-
let (fieldvalue, remainder) = match wire_type {
94-
_ => todo!("Based on the wire type, build a Field, consuming as many bytes as necessary.")
95-
};
96-
todo!("Return the field, and any un-consumed bytes.")
97-
}
98-
99-
.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-parse_message :end-before://ANCHOR-traits_solution :code:rust
100-
101-
.. code:: rust
102-
103-
// TODO: Implement ProtoMessage for Person and PhoneNumber.
104-
105-
-----------------------
106-
Protobuf Main Program
107-
-----------------------
9+
- Solve for compilation errors
10810

109-
.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-main :code:rust
11+
- Follow the hints!
11012

111-
--------------------
112-
Protobuf Solutions
113-
--------------------
13+
- Success is
11414

115-
.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-parse_field_solution :end-before://ANCHOR-parse_message :code:rust
15+
- Code that compiles
11616

117-
.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-traits_solution :end-before://ANCHOR-main :code:rust
17+
- ...and that follows any behavior indicated within the hints!

courses/rust_essentials/160_lifetimes/lab/answer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "lab"
2+
name = "answer"
33
version = "1.0.0"
44
edition = "2024"
55

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,128 @@
1+
//! Lab (answer)
2+
//! Lifetimes
3+
//!
4+
//! Fix all the compile errors below by following the hints provided
5+
//!
6+
7+
fn task_1() {
8+
println!("Task 1 - Lifetime");
9+
// Task 1 Goal
10+
// Ensure object lives as long as its reference
11+
// Hint:
12+
// Object should be in same scope as reference (or higher)
13+
let gold = 1000;
14+
{
15+
let treasure_map: &i32;
16+
treasure_map = &gold;
17+
println!("{treasure_map}");
18+
}
19+
println!("{gold}");
20+
}
21+
22+
fn task_2() {
23+
println!("Task 2 - Solving ambiguity");
24+
// Task 2 Goal
25+
// Fix the function so that it can return the reference
26+
// of the longer string
27+
// Hint:
28+
// Return value's lifetime is constrained by the shortest-lived string
29+
30+
fn choose<'a>(left: &'a str, right: &'a str) -> &'a str {
31+
// Return reference to longer of 'left' or 'right'
32+
if left.len() > right.len() {
33+
left
34+
} else {
35+
right
36+
}
37+
}
38+
39+
let longer12: &str;
40+
let longer23: &str;
41+
let longer13: &str;
42+
let input1 = String::from("medium");
43+
{
44+
let input2 = String::from("short");
45+
{
46+
let input3 = String::from("very long");
47+
longer12 = choose(&input1, &input2);
48+
longer23 = choose(&input2, &input3);
49+
longer13 = choose(&input1, &input3);
50+
println!("longer12 = {}", longer12);
51+
println!("longer23 = {}", longer23);
52+
println!("longer13 = {}", longer13);
53+
}
54+
}
55+
}
56+
57+
fn task_3() {
58+
println!("Task 3 - Lifetimes in structs");
59+
// Task 3 Goal
60+
// Fix the function to allow it to return a reference from within a struct
61+
// Hint:
62+
// Function needs to indicate where the borrowed data comes from
63+
// Struct needs to specify that the referenced element lasts as long as the struct
64+
65+
struct Owner {
66+
name: String,
67+
}
68+
struct User<'a> {
69+
name: &'a str,
70+
}
71+
72+
fn find_user<'a>(owner: &'a Owner, _extra_data: &str) -> User<'a> {
73+
User { name: &owner.name }
74+
}
75+
76+
let my_owner = Owner {
77+
name: String::from("Permanent"),
78+
};
79+
let result_user;
80+
{
81+
let short_lived_str = String::from("Temporary");
82+
result_user = find_user(&my_owner, &short_lived_str);
83+
}
84+
println!("{}", result_user.name);
85+
}
86+
87+
fn task_4() {
88+
println!("Task 4 - Lifetimes for enums");
89+
// Task 4 Goal
90+
// Fix the function to allow it to return a reference from within an enum variant
91+
// Hint:
92+
// Function needs to indicate where the borrowed data comes from
93+
// Enum needs to specify that the referenced variant lasts as long as the enum
94+
struct Owner {
95+
name: String,
96+
}
97+
98+
enum UserSession<'a> {
99+
_Admin,
100+
Guest(&'a str),
101+
}
102+
103+
fn get_session<'a>(owner: &'a Owner, _extra: &str) -> UserSession<'a> {
104+
UserSession::Guest(&owner.name)
105+
}
106+
107+
let my_owner = Owner {
108+
name: String::from("Permanent"),
109+
};
110+
111+
let result_session;
112+
{
113+
let short_lived_str = String::from("Temporary");
114+
result_session = get_session(&my_owner, &short_lived_str);
115+
}
116+
117+
match result_session {
118+
UserSession::Guest(name) => println!("Guest name: {}", name),
119+
UserSession::_Admin => println!("Logged in as Admin"),
120+
}
121+
}
122+
1123
fn main() {
2-
println!("TBD");
124+
task_1();
125+
task_2();
126+
task_3();
127+
task_4();
3128
}

courses/rust_essentials/160_lifetimes/lab/prompt/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "lab"
2+
name = "prompt"
33
version = "1.0.0"
44
edition = "2024"
55

0 commit comments

Comments
 (0)