Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving Code Idiomacy #268

Merged
merged 1 commit into from
Nov 30, 2023
Merged

Conversation

koopa1338
Copy link
Contributor

This pull request focuses on enhancing the idiomatic expression within the Rust codebase. By adhering more closely to Rust's best practices and conventions, this update aims to improve readability, maintainability, and performance where applicable.

Changes Made

  1. Iterator Usage: Utilized iterators and functional programming paradigms in certain loops, enhancing code clarity.
  2. Options: Utilize methods of options to avoid unnecessary if-else
  3. Constructors: Removed empty constructors and replaced them with their Default implementation
  4. Traits: Where possible, used impl Traits in function arguments and replaced methods with their semantic trait implementation
  5. Derives: Sorted derives and add Default where possible
  6. Modules: Replaced named module files that contained only module declaration with corresponding mod.rs files

@koopa1338 koopa1338 marked this pull request as draft November 19, 2023 22:01
@koopa1338 koopa1338 changed the title Draft: Improving Code Idiomacy Improving Code Idiomacy Nov 19, 2023
@Sharktheone
Copy link
Member

For point 6: We have moved from the mod.rs syntax in #107. It can get quite messy and chaotic when having 100 files called mod.rs, especially when you have multiple of them open in your editor.

pub fn read_fixture_from_path(path: &PathBuf) -> Result<FixtureFile, Error> {
let input = fs::read_to_string(path)?;
let path = path.to_string_lossy().into_owned();
pub fn read_fixture_from_path(path: impl AsRef<Path>) -> Result<FixtureFile, Error> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I'm disagreeing, but for my own knowledge (still new to rust) - what's the benefit of using AsRef here versus a "regular" & reference?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read through the rust docs, I guess it's for anything that can be converted to a PathBuf reference. I'm not familiar enough with this portion of the code to know if that's done, but I don't see the harm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read through the rust docs, I guess it's for anything that can be converted to a PathBuf reference. I'm not familiar enough with this portion of the code to know if that's done, but I don't see the harm

Exactly, I guess it isn't necessary at this point, I'm also not familiar with the code, so I only "fixed" things in a way that is common in Rust and I would also do myself. In this case it makes the api more flexible for future usage.

@Sharktheone
Copy link
Member

It breaks many of the tests tough, and you may consider running make format and fixing some cargo clippy warnings

@koopa1338
Copy link
Contributor Author

yeah it's still wip, I will make sure that all tests run again and the ci is happy 😉

@koopa1338 koopa1338 marked this pull request as ready for review November 20, 2023 22:15
@jaytaph
Copy link
Member

jaytaph commented Nov 22, 2023

@koopa1338 sorry for the delay. Didn't see it was still pending. If we have approval, we can merge the PR. If you like, you can add your name to the AUTHORS file as well.

@koopa1338
Copy link
Contributor Author

No worries, I added myself to the authors file 😉

@Kiyoshika
Copy link
Member

I looked through a good chunk of this earlier, will finish reviewing hopefully soon, trying to wrap up some of the bindings real quick

@Kiyoshika
Copy link
Member

@jaytaph do you want the commits squashed prior to merging? I know lately we usually stick to 1-2 commits per PR

Copy link
Member

@Sharktheone Sharktheone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like most of the changes, especially adding the "missing" semicolons. But i really don't like the things with default. I personally prefer new over default.

src/config.rs Outdated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, this will interfere with #267, as well as the other files, edited in this pr

Comment on lines +242 to +251
let (id, parent, children, name, namespace, is_registered) = <_>::default();
Node {
id: Default::default(),
parent: None,
children: vec![],
id,
parent,
children,
data: NodeData::Document(DocumentData::new()),
name: "".to_string(),
namespace: None,
name,
namespace,
document: Document::clone(document),
is_registered: false,
is_registered,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it would make sense to do this like this

Suggested change
let (id, parent, children, name, namespace, is_registered) = <_>::default();
Node {
id: Default::default(),
parent: None,
children: vec![],
id,
parent,
children,
data: NodeData::Document(DocumentData::new()),
name: "".to_string(),
namespace: None,
name,
namespace,
document: Document::clone(document),
is_registered: false,
is_registered,
Node {
data: NodeData::Document(DocumentData::new()),
document: Document::clone(document),
..Default::default()
}

You would need to implement Default for Node then, but this should be possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into it, and there are some issue with the default implementation that I couldn't solve, for example I would have to use a default for the NodeData and neither of the variants looks like a good fit for a default value imo. Any suggestions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, you could use a comment as a "Default" NodeData. But Document could also make sense, because it's an empty struct. I guess it's up to you, if no one as a other opinion on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would skip that for now, the PR is already big enough and I guess there might be more discussion about that, so I will open an Issue regarding this.

@@ -3667,7 +3667,7 @@ impl<'chars> Html5Parser<'chars> {
let new_name = SVG_ADJUSTMENTS_ATTRIBUTES
.get(name)
.expect("svg adjustments");
new_attributes.insert(new_name.to_string(), value.clone());
new_attributes.insert((*new_name).to_owned(), value.clone());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit odd, i mean defer the element and then call to_owned if you could just call to_string

Copy link
Contributor Author

@koopa1338 koopa1338 Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can see that this is kind of weird, It would be fine to revert this to to_string, however, we should use to_owned if we can, because to_string will use the generic ToString implementation and therefor use the fomratting functions, which end up doing multiple allocations. The to_owned function just allocates a buffer and copies the literal into that buffer.

The deref is necessary because we get a &&str out of the hashmap. Another option that might be better to read would be this:

let &new_name = SVG_ADJUSTMENTS_ATTRIBUTES
    .get(name)
    .expect("svg adjustments");
new_attributes.insert(new_name.to_owned(), value.clone());

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, i must have missed this... Good to know. Thanks for the explanation. I guess it makes sense to do it like in the code snippet you provided.

@Sharktheone
Copy link
Member

And can you maybe look at #107/#102 where we removed all the mod.rs files.

Copy link
Collaborator

@emwalker emwalker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@koopa1338 I like about 80 percent of the changes in this PR, and I can live with all of them. My main reservation is the size of the PR; in the future, perhaps tackling one thing at a time will make it easier to review.

Some of the changes are a little arbitrary, e.g., to_string() -> to_owned(), but I don't mind them, even if that's the case. This kind of update is a good way of familiarizing oneself with the code base.

For now, my own thought is to merge what you've done here. But first:

  1. There are merge conflicts that will need to be manually resolved
  2. Once the conflicts are resolved, you'll want to squash all commits into a single commit and force push

2023-11-23_10-52

@Sharktheone
Copy link
Member

Holy, this screenshot is like a flashbang... I can also live with all of the changes, but as I said above, I don't really like the "renaming" all new functions to default.

@jaytaph
Copy link
Member

jaytaph commented Nov 23, 2023

As far as I can tell, one of the benefits of having "Default" instead of New (considering new() would not have any arguments), is that it's easier to use them in scenario's like "unwrap_or_default() and such.

@Sharktheone
Copy link
Member

I mean you can have both new and default, you can just call new in default

@emwalker
Copy link
Collaborator

emwalker commented Nov 23, 2023

I personally don't mind a new(), and I think new() can even delegate to default():

impl Something {
  fn new() -> Self {
    Self::default()
  }
}

My own thought on the changes in this PR is that they're a vague improvement, because we're getting Default defined, and we're using it in a bunch of places where it makes sense. And the PR allows another developer to learn the code. We can always backfill new() back selectively as we go when it makes sense. But I'm just one developer here, so I'm happy to go with whatever other people think.

@koopa1338
Copy link
Contributor Author

For now, my own thought is to merge what you've done here. But first:

  1. There are merge conflicts that will need to be manually resolved
  2. Once the conflicts are resolved, you'll want to squash all commits into a single commit and force push

I would do another rebase, squash my commits and fix merge conflicts along the way.

@emwalker I was a bit unsure about the whole default and new thing, in general I thought that a new constructor without parameters could be replaced with a call to default, however there also some use cases where I see that a new constructor makes sense semantically, like for arenas or buffers.

I've stumbled on this clippy lint where the fix section seems like a sane alternative and if everyone is ok with that I would change it to that style.

@emwalker
Copy link
Collaborator

emwalker commented Nov 27, 2023

I would do another rebase, squash my commits and fix merge conflicts along the way.

Judging from experience, your rebase might be easier if you squash your commits first so that you only have one or two commits to rebase off of latest main. But I'm sure you'll figure something out.

E.g.,

$ git rebase -i "dd684aa502924ed0e86a70a768308f70e4e2089c^" # interactive rebase, squash everything here

I've stumbled on this clippy lint where the fix section seems like a sane alternative and if everyone is ok with that I would change it to that style.

Feel free to change some of those ::default() calls back to ::new(). On the off chance you were thinking of updating the linting rules, I'd skip that for this PR.

@koopa1338 koopa1338 force-pushed the refactor/idiomatic-rust branch 3 times, most recently from 37584b2 to dfac1d8 Compare November 29, 2023 20:11
@jaytaph
Copy link
Member

jaytaph commented Nov 29, 2023

@emwalker @Sharktheone @Kiyoshika CI is happy.. feel free to merge

Copy link
Member

@Sharktheone Sharktheone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only some small changes, I might have missed last time.

Comment on lines 11 to +14
impl Default for Rectangle {
fn default() -> Self {
Self::new()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you use #[derive(Default)] here because the default rectangle uses zeros for the new method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I could, I just wanted to be consistent with the other default implementations. A pro imo is that, if you change the new constructor to accept parameters, then you are forced to reason about what the default implementation should do, or you even need one. If we derive the trait, then this connection is lost and this could lead to surprises in scenarios where you called default instead of new.

@Sharktheone
Copy link
Member

@emwalker @Sharktheone @Kiyoshika CI is happy.. feel free to merge

Yup, LGTM. I only found one small thing, but it wouldn't be problematic if it wouldn't be changed.

@Sharktheone Sharktheone mentioned this pull request Nov 29, 2023
Copy link
Collaborator

@emwalker emwalker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emwalker @Sharktheone @Kiyoshika CI is happy.. feel free to merge

We still need to rebase into a single commit. @jaytaph I think you still don't like using the "Squash and merge" button? Please advise. We want atomic commits that will make using git bisect unproblematic.

@koopa1338 can you rebase this PR into a single commit and force push the result back to your branch?

2023-11-29_16-46

- use mod.rs instead of empty named module file
- replace emtpy new constructor with defaulf implementations
- sorting derives
- replaced ElementClass::from_string method with From trait impl
- rename `Buffer::to_string` to `try_to_string` to indicate possible failure
- default impl for `TotalTestResutls`
- make function arguments more readable/flexible
- better usage of iterators and options, as well as minor improvements
- remove iter calls in for loops
- asserts, matches and if let bindings
- must use attributes
- strings and closures
- debug and default impls
- borrows in arguments
- filter iterators
- make use of `Self` keyword in impl blocks
@koopa1338
Copy link
Contributor Author

Sorry for that, didn't know that the squash and commit is disabled, squashed it all in a single commit, should be fine now.

Copy link
Collaborator

@emwalker emwalker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Congratulations, @koopa1338, on your first Gosub PR. Many thanks for the help.

@emwalker emwalker merged commit 45593c6 into gosub-io:main Nov 30, 2023
4 checks passed
@koopa1338 koopa1338 deleted the refactor/idiomatic-rust branch November 30, 2023 08:29
@Kiyoshika Kiyoshika mentioned this pull request Dec 1, 2023
Sharktheone added a commit to Sharktheone/gosub-engine that referenced this pull request Dec 14, 2023
@Sharktheone Sharktheone mentioned this pull request Dec 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants