King Triton’s Ocean
you will be implementing some unfinished Rust code which models a mythical ocean ecosystem populated by crabs that live on beaches, and various prey they hunt in reefs.
These crabs have names, and come in any and all colors. Groups of them live together (and reproduce) at beaches along the coast line. Once the sun sets, they head out to their favorite reefs to hunt for food that matches their tastes. Some crabs only eat fish, some only shellfish, while yet others are vegetarian.
Luckily, the reefs dotting this ocean are populated with plenty of prey that fit into each crab’s dietary preferences. However, finding the right reef with the right food can prove a challenge.
It’s a tough life for a crab out there.
Learning Goals
By the end of this assignment you should have gained some hands-on experience the basics of the Rust language, as well as some features peculiar to its ownership model.
- Functions
- Structs
- Methods
- Trait Objects
- Mutable vs Immutable References
- Smart Pointers
- Single Ownership (Boxes)
- Multiple Ownership (Reference-counted Boxes)
This assignment is not intended to be algorithmically challenging, but rather to be something of a guided tour through some of Rust’s distinct characteristics. No individual task should be too complicated to implement, and almost all have corresponding tests you can use to check your progress as you work.
Instructions
If you are unsure about how to do something in Rust, you should probably first consult the Rust Book.
Submission
You should only modify and submit the following files:
beach.rscolor.rscrab.rsocean.rsreef.rs
Any changes made to other files will be ignored by the autograder, which will always use the originals.
Running Tests
You can run all tests with cargo test, and all tests with a given prefix with cargo test <prefix>.
To make things easy, all of the tests in this assignment are named according to the following convention:
cargo test part${n}_${entity_name}_${tested_behavior}
For example, to run all tests that relate to Crab from Part 1, you can simply run:
cargo test part1_crab
By default, cargo captures the console output of your code while running tests. However, while working on this assignment you might want to use print! or println! while running your tests to debug. You can do this by passing the --nocapture flag while running tests after --, which separates test names from flags.
For example, to run tests involving the crab hunting tasks in Part 2 while showing console output:
cargo test part2_crab_hunt -- --nocapture
You should read the tests carefully. They may serve as examples of unfamiliar syntax, and even provide some useful insights into how to implement some of the later tasks.
Writing Tests
You should not modify the contents of the tests/public.rs file.
However, you may find it useful to write your own tests in tests/student.rs.
These tests will be ignored by the autograder, so don’t worry if they don’t pass.
Part 1. Basic
We will first implement a simple model of a Crab, and then a Beach which contains zero or more crabs. In this manner, we demonstrate how to define and work with single objects as well as collections in Rust. We will also implement a function for breeding crabs.
For this part, you will not have to worry about lifetimes, and ownership should be straightforward: just simple borrows and dereferences as shown in lectures.
Tasks
Simple Functions and Methods
First, implement the missing bits in crab.rs and color.rs:
color.rs- The
crossfunction, which crosses two colors by performing wrapped arithmetic. - Refer to the specification in the function’s doc string for more details. Note that Rust, being a “safe” language, handles arithmetic operations much more strictly than you may be used to, and the “obvious” solution here is not the correct one and will panic.
- Documentation for Primitive Type u8
- The
crab.rs- A
newfunction according to the given signature. - The
name,speed,coloranddietfields onCraband corresponding accessor functions.
- A
You should now have the following tests passing:
part1_color_cross_no_panicpart1_crab_new
Crabs & Braches
Then, implement the missing functions in beach.rs:
- A
newfunction according to the given signature. size: returns the number ofCrabs on the beach.add_crab: adds aCrabto the beach.get_crab: returns a reference to aCrabat the given index.get_fastest_crab: returnsNoneif the beach is empty. Otherwise, returnSomeof a reference to theCrabwith the highestspeed.breed_crabs: uses the functions indietandcolorsto breed two crabs, resulting in a newCrab.- You will want to add a new
breedfunction toCrabto avoid exposingCrabimplementation details. - The new
Crabshould have its diet selected randomly using the providedDiet::random_diet. - The new
Crabshould have its color computed byColor::crossfrom before. - The new
Crabshould have a speed of1(babies go slowly).
- You will want to add a new
find_crabs_by_name: returns a vector of references to crabs with the given name.
The tests for these functions are:
part1_beach_add_get_crabpart1_beach_iter_crabspart1_beach_sizepart1_beach_fastest_emptypart1_beach_fastest_fastest_firstpart1_beach_fastest_fastest_secondpart1_crab_breedingpart1_crab_find_by_name
Notes
- If you are accustomed to functional-style programming, you may find methods on std::slice::Iter useful or more natural to implement
get_fastest_crab,find_crabs_by_name, and some functions in the next part.
Part 2. Advanced
While working on this section, you should consult some sections of the Rust Book for guidance and examples.
We will make use of trait objects to represent Prey that Crabs go out and hunt. Trait objects are often used in similar places to interfaces, abstract classes in other languages like Java, Go, and C++, though they differ in some ways. They allow us to refer to something which implements a given trait.
We will also make use of smart pointers to heap allocations: both single-ownership Box<T>, and multiple-ownership (reference-counted) Rc<T>. The referents of smart pointers in Rust are immutable. Therefore, in order to represent a pointer to something mutable, we use Box<RefCell<T>> or Rc<RefCell<T>>. This is a common, but cumbersome, design pattern in Rust that results from some of the choices made in the design of the language.
Things to Note in the Starter Code
In prey.rs note (but not modify) the definitions:
pub trait Prey: defines the interface which allPreymust implement.- The
dietmethod returns the kind ofDietthisPreyis compatible with. - The
try_escapemethod returns whether or not thisPreycan escape from a givenCrab, in a givenReef.- This method is side-effecting, and can therefore mutate the
Preyit is called on, such as with theShrimp.
- This method is side-effecting, and can therefore mutate the
- The
In reef.rs, you should take note of a few facts:
- A
VecDequeis Rust’s standard library queue structure.- It can be used as a double-ended queue, but here we are only using it as a single-ended queue.
- We had to declare the
preyfield ofstruct Reefusing thedynkeyword.- However,
VecDeque<dyn Prey>would not work, because the size in memory ofdyn Preyis not known at compile time. Therefore, we useBox<dyn Prey>, which represents a pointer to something implementingPreyon the heap.
- However,
Tasks
Lifetimes with Recipes
First, let’s look at a simple example of where explicit lifetime annotations are needed. It turns out that our crabs like to cook their food before they eat it (this is a mythical ocean, remember?). In crab.rs implement:
choose_recipe: returns a recipe from theCookbookthat matches theCrab’s diet.- This is the only case in this assignment where you will need to modify the signature of a method.
- You should only add lifetime annotations.
Implementing Reefs
Next, let’s implement the missing methods on reef.rs. A Reef is a wrapper around a queue of Box<dyn Prey>. We use a queue here as we will later have a Crab take Prey from the Reef, and potentially put it back. Using a queue removes the need to keep track of indices and supports both fast insertion and removal at the ends.
newadd_prey: push prey to the back of the reef.take_prey: pop prey from the front of the reef.
At this point the test part2_reef_add_take_prey should pass.
Next, add a reefs field to struct Crab with an appropriate type. Consult discover_reef for some ideas. Note that multiple crabs can share the same Reef, and both can mutate it. We represent a reference-counter/multiply-owned pointer with Rc, and a cell containing a mutable reference with RefCell. Put together: Rc<RefCell<Reef>>.
Then, in crab.rs, implement:
discover_reef: add a reef to aCrab’s list of reefs.- There is no corresponding
forget_reef, but you are welcome to add one if it helps you write your own tests. We will ignore it.
- There is no corresponding
At this point the test part2_crab_discover_reefs should pass.
Catching Prey and Going Hunting
Crabs in this ocean hunt in a very particular way. They are greedy, but they will never try to catch the same prey twice. In this manner, they avoid getting stuck chasing the same uncatchable prey all day long. When a Crab catches Box<dyn Prey>, it removes it from the reef, since a Box can only have one owner.
catch_prey: catch somePreyfrom one of theCrab’s reefs and returnSomeof it and the index of theReefit came from, orNoneif none can be found.- Consult the docstring for specific details.
- In order to call
take_preyon one of theReefs stored in aRc<RefCell<Reef>>, you will need to borrow mutably from it.- Consult the documentation and relevant Rust book chapter to figure out which method to use.
- You may also find it helpful to look at some of the tests.
release_prey: releasePreyback into theReefat the given index.hunt: implements the hunting behavior of the crab.- Consult the docstring for full pseudocode for this method, as it is a bit more involved than any so far.
Note that the latter of these two methods are not public. They are implementation details, and helper functions to implement hunt.
You should now have the following tests passing:
part2_crab_hunt_empty_reefpart2_crab_hunt_escaped_preypart2_crab_hunt_incompatiblepart2_crab_hunt_success
The Ocean Itself
So far, you’ve been tasked with using smart pointers from the callee side. That is, you have always received them from elsewhere, but have not had to construct them or in the case of Rc take additional references to them yourself using Rc::clone.
We will now define Ocean, which provides an interface to many of the definitions implemented so far.
First, in ocean.rs, add appropriate struct fields to back the beaches and reefs accessors. Then, implement the missing methods:
newadd_beach: adds a beach to thisOceanbeaches: returns an iterator over thisOcean’s beaches.reefs: returns an iterator over thisOcean’s reefs.generate_reef: generates a reef with the number of each type of prey specified in the arguments, adds a reference to it to thisOcean’s reefs, and returns a reference.- This will require creating both kinds of smart pointers used so far (
Box,Rc) as well asRefCell. - Note that you will need to get a second reference to an
Rchere as well, since you need one to add toreefs, and one to return.
- This will require creating both kinds of smart pointers used so far (
Tip: start by considering Algae only, getting part2_ocean_generate_algae_only passing.
You should now have the following tests passing:
part2_ocean_generate_algae_onlypart2_ocean_generate_algae_bountiful
Clan Competitions
The crabs in King Triton’s Ocean like to socialize by forming different clans and competing among themselves. Using the clan system, the crabs can join a clan and engage in the competition. Multiple crabs can belong to the same clan, but a crab cannot join more than one clan. Every beach has a clan system in place.
Your task is to extend the existing model for beaches and clans. Note: Every clan has a unique clan id, and for the purposes of this task, every crab will have a unique name.
In clans.rs , add appropriate fields, add a new method, and complete the following methods with the given signatures:
get_clan_member_names: returns a list of the member names for the given clanid.get_clan_count: returns the number of clans currently in existence.get_clan_member_count: returns the number of members for the given clanid.get_largest_clan_id: returns theidof the clan with the most number of crabs, orNoneif such a clan does not exist.
In beach.rs, add a field for the ClanSystem, update existing methods where necessary and complete the following methods with the given signatures:
get_clan_system: returns a reference to the clan system associated with the beach.add_member_to_clan: adds a crab that lives on the beach as a member for the given clanidand crabname. If a clan with thisiddoes not exist, create one. You may define and call a method inclans.rsto perform this operation.get_winner_clan: returns theidof the clan that wins the competition given two clan ids and returns aResulttype. The winner is decided based on the average speed of the clan members. ReturnNoneif there are no clear winners between two different existing clans. If the inputs are invalid, return anErrstring.
You should now have the following tests passing:
part3_clans_get_clan_member_names_zeropart3_clans_get_clan_member_namespart3_clans_get_clan_member_count_firstpart3_clans_get_clan_member_count_allpart3_clans_get_clan_count_zeropart3_clans_get_clan_count_allpart3_clans_get_largest_clan_id_empty_beachpart3_clans_get_largest_clan_id_firstpart3_clans_get_largest_clan_id_secondpart3_beach_get_winner_clan_empty_beachpart3_beach_get_winner_clan_tiepart3_beach_get_winner_clan
Submitting your Work
That’s it! You’re done!
You should double check your tests all pass by ensuring all files have been saved and then running cargo clean and cargo test.
You should submit the following files to Gradescope:
beach.rscolor.rscrab.rsocean.rsreef.rs
599

被折叠的 条评论
为什么被折叠?



