DEV Community

TheoForger
TheoForger

Posted on

A Rusty Three-Way Merge

Continuing on the work on my project Mastermind, This week our task was to implement two new features on two separate branches, and then merge them both into main. Of course, this will subsequently create a three way recursive merge situation.

New Features

Issue #16 Implemented by Commit d6d8bfc

Prior to this implementation, the project only support one language model at a time. Because LLMs tend to hallucinate a lot, the results can be underwhelming.

The idea is to give users the option to choose multiple language models and aggregate them, so the results are more diverse, and the users can compare them.

This feature was a lot more work that I had anticipated - At that stage, the project could only handle one API call at a time. So first I had to figure out a way to process multiple API calls, and then allow different models.

In addition to that, now that I had answers from different sources, I thought it's best to keep track of the sources as well, so I added a source field to the output:

╭────────┬───────┬───────────────────────────────┬────────────────────╮
│  Clue  ┆ Count ┆          Linked Words         ┆       Source       │
╞════════╪═══════╪═══════════════════════════════╪════════════════════╡
│ park   ┆   3   ┆ scuba diver, walrus, hospital ┆ gemma2-9b-it       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ bond   ┆   3   ┆ tokyo, bee, hospital          ┆ gemma2-9b-it       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ dive   ┆   3   ┆ scuba diver, sound, hospital  ┆ mixtral-8x7b-32768 │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ sound  ┆   2   ┆ walrus, penny                 ┆ gemma2-9b-it       │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ sound  ┆   2   ┆ bond, bee                     ┆ mixtral-8x7b-32768 │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ park   ┆   2   ┆ hospital, tokyo               ┆ mixtral-8x7b-32768 │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ penny  ┆   2   ┆ tip, goldilocks               ┆ mixtral-8x7b-32768 │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ walrus ┆   2   ┆ cotton, scarecrow             ┆ mixtral-8x7b-32768 │
╰────────┴───────┴───────────────────────────────┴────────────────────╯
Enter fullscreen mode Exit fullscreen mode

Issue #17 - Implemented by Commit 4ee3464

This feature came to mind when I first implemented -g to retrieve available language models, and then -m <model-name> to specify a model. I immediately realized how tedious it was - You have to run two commands to do one task. Not the best workflow.

So I had this idea: If you run it with the -m option without giving any model names, instead of showing an error, the program will prompt an interactive selection menu:

[Space] to select, [Enter] to confirm
Your choice(s):
> [ ] distil-whisper-large-v3-en
  [ ] gemma-7b-it
  [ ] gemma2-9b-it
  [ ] llama-3.1-70b-versatile
  [ ] llama-3.1-8b-instant
  [ ] llama-3.2-11b-text-preview
  [ ] llama-3.2-11b-vision-preview
  [ ] llama-3.2-1b-preview
  [ ] llama-3.2-3b-preview
  [ ] llama-3.2-90b-text-preview
  [ ] llama-guard-3-8b
  [ ] llama3-70b-8192
  [ ] llama3-8b-8192
  [ ] llama3-groq-70b-8192-tool-use-preview
  [ ] llama3-groq-8b-8192-tool-use-preview
  [ ] llava-v1.5-7b-4096-preview
  [ ] mixtral-8x7b-32768
  [ ] whisper-large-v3
Enter fullscreen mode Exit fullscreen mode

Initially, I thought this would make the standard -g and -m <model-name> features obsolete. After I demonstrated to my professor, he thoughtfully suggested that a non-interactive mode is good for automation purposes.

One problem happened during the development: Even though I did my work separately on two branches, some of the changes were not made with the correct base (the main branch) in mind - I made the selection menu with the assumption that the program already supports multiple language models.

I only realized that when I call the menu in the main function. The new code is simply not compatible with my base. Luckily, I eventually figured out a workaround.

Three-Way Merge

Before the merge I knew there were going to be some problems, since the two branches worked on the same part of the project.

The first merge was from the feature/issue-16 branch, which was easy, since it was a fast-forward merge.

The three-way-recursive merge happened when I merged the feature/issue-17 branch. To my surprise, only two files had conflicts - I think my project is getting to a point where it's too big for me to fully understand. I combined new dependencies from the two branches, and inserted some new code. Things mostly just worked.

However, because of the "workaround" I coded in the issue 17 branch, there was still more work to do to iron out the rough edges - I guess not all problems can be resolved during a merge. I made some additional commits before pushing my repo to GitHub.

A Screenshot of my GitHub commit history

Some Other Things I Learned

  • Getters and setters are not recommended for Rust, since they get in the way of Rust's borrow checker. The best practice to access the fields is to make them public (opposite to most OOP languages)
  • Access to an element inside a vector is always a reference. So you can't take ownership of it.
  • unwrap() means "Ok() or panic". ? means "Ok() or return error"
  • It's schematically a better idea to implement the Display trait rather than writing a display() method
  • If you don't need the Option<T> or Result<T> themselves, when handling them, use shadowing to avoid naming confusion
  • If You're not sure about a merge, create a dummy branch on main, and merge into that branch instead. If the result is good, simply merge the dummy branch into main. If not, just delete it
  • Don't forget to update unit testing whenever you make a change to the code

Top comments (0)