This is a large post, and I realize there are many topics. Out of respect of your own time, please don't reply to every one of my concerns. Any response is appreciated.
I recently stumbled on a comment on r/learnprogramming that really hit home for me:
Most software is built by a team, not an individual. If you work on a project with more than one software engineer, you'll spend a lot of time collaborating with them - reviewing code, discussing how to divide things up, analyzing bugs.
As someone who is self learning, having support from a team is something that I desire so much. Like the thought of having a team to talk to when struggling to solve a problem. Or having someone point out flaws in my code, which would allow me ask myself the right questions.
I have been reading/ studying Rust for a while, but really needed to begin writing more Rust! I find it difficult to know where to start projects, especially with limited capabilities.
I finally came up with something...
I stumbled on an idea where I needed a data structure that allows me to customly define a range with a known position.
I assume something like this already exists, but I thought it would be a good exercise (By the way, if you know of an existing crate that already achieves this, please share).
My goal for this project is to write idiomatic Rust.
Context:
This struct will be used inside a game I am building. I needed a way to represent a a custom defined range that knows its current location.
For instance, I could define an attribute called confidence
:
let mut confidence = RangePointer {
min: 0,
max: 100,
current: 50
}
As the player interacts with game, their confidence can increase or decrease depending on an event.
Overview:
Functions include:
pub fn new() -> Self {}
pub fn increase(mut self) -> Result<u16, RangeError> {}
pub fn increase_by(mut self, amount: u16) -> Result<u16, RangeError> {}
pub fn decrease(mut self) -> Result<u16, RangeError> {}
pub fn decrease_by(mut self, amount: u16) -> Result<u16, RangeError> {}
pub fn randomize(mut self) -> Result<u16, RangeError> {}
pub fn minimize(mut self) -> Result<u16, RangeError> {}
pub fn maximize(mut self) -> Result<u16, RangeError> {}
Snippet:
Below is a snippet of my code, the whole project can be seen here on github. I commented on the areas that might be a questionable design choice. If you have any advice, even if it is a simple one liner, please share your thoughts. Thank you
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct RangePointer {
// Thought about making the type generic,
// but wouldn't this require building a separate trait?
pub min: u16,
pub max: u16,
pub current: u16
}
impl RangePointer {
// What are your thoughts on returning a `Result`? Are there downsides to this approach?
pub fn new(min: u16, max: u16, current: u16) -> Self { // -> Result<RangePointer, RangeError>
// Assert min < max, throw relevant error message
// Assert current >= min and <= max, throw relevant error message
RangePointer{ min, max, current }
}
// What are your thoughts on `mut self`? Should I consider `&mut self`?
pub fn increase(mut self) -> Result<u16, RangeError> {
let value = match self.current.overflowing_add(1) {
(_, true) => self.max,
(value, false) => value,
};
// Is there a more concise way to implement this `if` expression?
if value > self.max {
self.current = self.max;
} else {
self.current = value;
}
Ok(self.current)
}
// This is my first custom error that I have ever wrote, and I referenced The Book
// Can this be improved?
// Would it be better to use a boxed std::error::Error?
#[derive(Debug, Clone)]
pub struct RangeError {
pub message: String
}
impl fmt::Display for RangeError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self.message)
}
}
impl std::error::Error for RangeError {
fn description(&self) -> &str {
&self.message
}
}
#[cfg(test)]
mod tests {
#[test]
pub fn test_increase() {
let r = RangePointer{min: 1, max: 6, current: 1};
let a = r.increase().unwrap();
assert_eq!(a, 2);
}
#[test]
pub fn test_increase_limit() {
let r = RangePointer{min: 1, max: 6, current: 6};
let a = r.increase().unwrap();
assert_eq!(a, 6);
}
#[test]
pub fn test_increase_overflow() {
let r = RangePointer{min: 1, max: u16::MAX, current: u16::MAX};
let a = r.increase().unwrap();
assert_eq!(a, u16::MAX);
}
}
P.S.
I am very interested in learning how to write Clean Hexagonal Architecture with Data Oriented Design, but have trained myself to think in OOP. With time, I would like to retrain myself. If you see anything inside my code that would be a step in the right direction, please let me know.