diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 21f9f1c3c0da4bd347bf568a65f3bfc53df3b030..71b24288d578fa091b19511661cf940a93372c2a 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -18,6 +18,7 @@ name = "amalib"
 version = "0.1.0"
 dependencies = [
  "commons",
+ "getset",
  "serde",
  "tokio",
 ]
@@ -259,6 +260,16 @@ dependencies = [
  "pin-utils",
 ]
 
+[[package]]
+name = "getset"
+version = "0.1.2"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "h2"
 version = "0.3.15"
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index 0b4b217d6d6e822811b900c682c12ce0a1fec4d1..f8363c81dc645e9c6c0afcf521c0822eb7734fd5 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -11,6 +11,7 @@ members = [
     # Common things
     "kurisu_api",
     "commons",
+    "getset",
 
     # Clients
     "amadeus",
diff --git a/src/rust/amalib/Cargo.toml b/src/rust/amalib/Cargo.toml
index 6def1de2e7e71aa0e950abd6a0cad47115f265f2..9cee7e7860571c0b82aba6cbbb2d4008c7c42ff8 100644
--- a/src/rust/amalib/Cargo.toml
+++ b/src/rust/amalib/Cargo.toml
@@ -10,3 +10,4 @@ serde.workspace = true
 tokio.workspace = true
 
 commons = { path = "../commons" }
+getset = { path = "../getset" }
diff --git a/src/rust/amalib/src/response.rs b/src/rust/amalib/src/response.rs
index 460cd0ce40f6092db44cd36509e3c1ddd245faa5..ecd326c307e7960faa2117b2da2ad0790f616285 100644
--- a/src/rust/amalib/src/response.rs
+++ b/src/rust/amalib/src/response.rs
@@ -7,8 +7,8 @@ use crate::*;
 /// spaces from the keys and the values. The keys are always in lowercase.
 #[derive(Debug)]
 pub struct LektorFormatedResponse {
-    content: Vec<(String, String)>,
-    raw_content: Vec<String>,
+    dict: Vec<(String, String)>,
+    raw: Vec<String>,
 }
 
 impl LektorFormatedResponse {
@@ -16,12 +16,12 @@ impl LektorFormatedResponse {
     /// found. If multiple keys are found, only the first found is returned.
     pub fn pop(&mut self, key: &str) -> Result<String, String> {
         match self
-            .content
+            .dict
             .iter()
             .enumerate()
             .find_map(|(index, (what, _))| (what == key).then_some(index))
         {
-            Some(index) => Ok(self.content.remove(index).1),
+            Some(index) => Ok(self.dict.remove(index).1),
             None => Err(format!("no key {key} was found in formated response")),
         }
     }
@@ -30,7 +30,7 @@ impl LektorFormatedResponse {
     /// vector is returned. This function can't fail.
     pub fn pop_all(&mut self, key: &str) -> Vec<String> {
         let mut ret: Vec<String> = Vec::new();
-        self.content.retain(|(what, field)| {
+        self.dict.retain(|(what, field)| {
             if *what == key {
                 ret.push(field.clone());
                 false
@@ -45,12 +45,12 @@ impl LektorFormatedResponse {
     /// [`LektorFormatedResponse::pop_raw`] function, but we splited each line
     /// on the `: ` patern, and trimed all the things.
     pub fn pop_dict(self) -> Vec<(String, String)> {
-        self.content
+        self.dict
     }
 
     /// Get the raw content of the response.
     pub fn pop_raw(self) -> Vec<String> {
-        self.raw_content
+        self.raw
     }
 }
 
@@ -60,7 +60,7 @@ impl IntoIterator for LektorFormatedResponse {
         <std::vec::Vec<(std::string::String, std::string::String)> as IntoIterator>::IntoIter;
 
     fn into_iter(self) -> Self::IntoIter {
-        self.content.into_iter()
+        self.dict.into_iter()
     }
 }
 
@@ -76,8 +76,8 @@ impl TryFrom<Vec<String>> for LektorFormatedResponse {
             }
         }
         Ok(Self {
-            content,
-            raw_content,
+            dict: content,
+            raw: raw_content,
         })
     }
 }
@@ -116,21 +116,8 @@ mod private {
     impl Sealed for LektorEmptyResponse {}
 }
 
-macro_rules! getter {
-    ($name: ident: ref $type: ty) => {
-        pub fn $name(&self) -> &$type {
-            &self.$name
-        }
-    };
-
-    ($name: ident: $type: ty) => {
-        pub fn $name(&self) -> $type {
-            self.$name
-        }
-    };
-}
-
-#[derive(Debug)]
+#[derive(Debug, getset::CopyGetters)]
+#[getset(get_copy = "pub")]
 pub struct LektorPlaybackStatusResponse {
     elapsed: usize,
     songid: Option<usize>,
@@ -151,15 +138,18 @@ pub struct LektorPlaylistSetResponse {
     playlists: Vec<String>,
 }
 
-#[derive(Debug)]
+#[derive(Debug, getset::Getters)]
+#[getset(get = "pub")]
 pub struct LektorCurrentKaraInnerResponse {
     title: String,
     author: String,
     source: String,
     song_type: String,
-    song_number: Option<usize>,
     category: String,
     language: String,
+
+    #[getset(get_copy = "pub")]
+    song_number: Option<usize>,
 }
 
 #[derive(Debug)]
@@ -177,7 +167,8 @@ pub struct LektorIntegerResponse {
     content: usize,
 }
 
-#[derive(Debug)]
+#[derive(Debug, getset::Getters)]
+#[getset(get = "pub")]
 pub struct LektorCurrentKaraResponse {
     content: Option<LektorCurrentKaraInnerResponse>,
 }
@@ -186,8 +177,6 @@ pub struct LektorCurrentKaraResponse {
 pub struct LektorEmptyResponse;
 
 impl LektorCurrentKaraResponse {
-    getter!(content: ref Option<LektorCurrentKaraInnerResponse>);
-
     pub fn maybe_into_inner(self) -> Option<LektorCurrentKaraInnerResponse> {
         self.content
     }
@@ -197,30 +186,7 @@ impl LektorCurrentKaraResponse {
     }
 }
 
-impl LektorCurrentKaraInnerResponse {
-    getter!(title: ref String);
-    getter!(author: ref String);
-    getter!(source: ref String);
-    getter!(song_type: ref String);
-    getter!(song_number: Option<usize>);
-    getter!(category: ref String);
-    getter!(language: ref String);
-}
-
 impl LektorPlaybackStatusResponse {
-    getter!(elapsed: usize);
-    getter!(songid: Option<usize>);
-    getter!(song: Option<usize>);
-    getter!(volume: usize);
-    getter!(state: LektorState);
-    getter!(duration: usize);
-    getter!(updating_db: usize);
-    getter!(playlistlength: usize);
-    getter!(random: bool);
-    getter!(consume: bool);
-    getter!(single: bool);
-    getter!(repeat: bool);
-
     pub fn verify(&self) -> bool {
         self.elapsed() <= self.duration()
     }
diff --git a/src/rust/getset/.github/actions-rs/grcov.yml b/src/rust/getset/.github/actions-rs/grcov.yml
new file mode 100644
index 0000000000000000000000000000000000000000..581dfdd4cd0891ad605bb478352923cc46f57dc8
--- /dev/null
+++ b/src/rust/getset/.github/actions-rs/grcov.yml
@@ -0,0 +1,10 @@
+branch: true
+ignore-not-existing: true
+llvm: true
+filter: covered
+output-type: lcov
+output-file: /tmp/lcov.info
+ignore:
+  - "/*"
+  - "C:/*"
+  - "../*"
\ No newline at end of file
diff --git a/src/rust/getset/.github/workflows/audit.yml b/src/rust/getset/.github/workflows/audit.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1fbe6239ccccd2dcd4ae64838cbed983d9ec27da
--- /dev/null
+++ b/src/rust/getset/.github/workflows/audit.yml
@@ -0,0 +1,21 @@
+name: audit
+
+on:
+  schedule:
+    - cron: '0 0 * * *'
+  push:
+
+jobs:
+  audit:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - name: Cache target
+        id: cache-target
+        uses: actions/cache@v1
+        with:
+          path: target
+          key: ${{ runner.os }}-audit-target-${{ hashFiles('**/Cargo.toml') }}
+      - uses: actions-rs/audit-check@v1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/src/rust/getset/.github/workflows/test.yml b/src/rust/getset/.github/workflows/test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8c20e2c79153bda9d4dda8f66f58da3d0918060f
--- /dev/null
+++ b/src/rust/getset/.github/workflows/test.yml
@@ -0,0 +1,72 @@
+name: test
+
+on:
+  push:
+  pull_request:
+
+env:
+  RUSTFLAGS: "--deny=warnings"
+
+jobs:
+  test:
+    strategy:
+      matrix:
+        os: [macos-latest, windows-latest, ubuntu-latest]
+        rust: [stable, nightly]
+    runs-on: ${{ matrix.os }}
+    steps:
+    - uses: actions/checkout@v1
+    - name: Cache target
+      id: cache-target
+      uses: actions/cache@v1
+      with:
+        path: target
+        key: ${{ runner.os }}-${{ matrix.rust }}-test-target-${{ hashFiles('**/Cargo.toml') }}
+    - name: Install Rust toolchain
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: ${{ matrix.rust }}
+    - name: Test
+      uses: actions-rs/cargo@v1
+      with:
+        command: test
+        args: -- --nocapture
+
+  clippy:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - name: Cache target
+        id: cache-target
+        uses: actions/cache@v1
+        with:
+          path: target
+          key: ${{ runner.os }}-clippy-target-${{ hashFiles('**/Cargo.toml') }}
+      - name: Install Rust toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+      - run: rustup component add clippy
+      - uses: actions-rs/clippy-check@v1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+
+  format:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - name: Cache target
+        id: cache-target
+        uses: actions/cache@v1
+        with:
+          path: target
+          key: ${{ runner.os }}-format-target-${{ hashFiles('**/Cargo.toml') }}
+      - name: Install Rust toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+      - run: rustup component add rustfmt
+      - uses: actions-rs/cargo@v1
+        with:
+          command: fmt
+          args: -- --check
diff --git a/src/rust/getset/Cargo.toml b/src/rust/getset/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..b6b65079c2a35d3d37619ccd1c79c4e6539957f4
--- /dev/null
+++ b/src/rust/getset/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "getset"
+description = """
+Getset, we're ready to go!
+
+A procedural macro for generating the most basic getters and setters on fields.
+"""
+version = "0.1.2"
+authors = ["Ana Hobden <ana@hoverbear.org>"]
+license = "MIT"
+edition = "2018"
+
+categories = ["development-tools::procedural-macro-helpers"]
+keywords = ["macro", "getter", "setter", "getters", "setters"]
+readme = "README.md"
+repository = "https://github.com/Hoverbear/getset"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "1"
+syn = "1"
+proc-macro2 = { version = "1", default-features = false }
+proc-macro-error = "1.0"
diff --git a/src/rust/getset/LICENSE b/src/rust/getset/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..76f4968430db086691c4f8cf47fdd65dc908373b
--- /dev/null
+++ b/src/rust/getset/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Ana Hobden <ana@hoverbear.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/rust/getset/examples/simple.rs b/src/rust/getset/examples/simple.rs
new file mode 100644
index 0000000000000000000000000000000000000000..62f072f37afeb751e6bcd63a01867524beb7c110
--- /dev/null
+++ b/src/rust/getset/examples/simple.rs
@@ -0,0 +1,24 @@
+use getset::{CopyGetters, Getters, MutGetters, Setters};
+
+#[derive(Getters, Setters, MutGetters, CopyGetters, Default)]
+pub struct Foo<T>
+where
+    T: Copy + Clone + Default,
+{
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[getset(get, set, get_mut)]
+    private: T,
+
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[getset(get_copy = "pub", set = "pub", get_mut = "pub")]
+    public: T,
+}
+
+fn main() {
+    let mut foobar = Foo::default();
+    foobar.set_private(1);
+    (*foobar.private_mut()) += 1;
+    assert_eq!(*foobar.private(), 2);
+}
diff --git a/src/rust/getset/src/generate.rs b/src/rust/getset/src/generate.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c4069617cbd2a7efdc4a2ca5a2fc3fb92d5f36e2
--- /dev/null
+++ b/src/rust/getset/src/generate.rs
@@ -0,0 +1,197 @@
+use self::GenMode::*;
+use super::{parse_attr, quote};
+use proc_macro2::TokenStream as TokenStream2;
+use proc_macro2::{Ident, Span};
+use proc_macro_error::{abort, ResultExt};
+use syn::{self, ext::IdentExt, spanned::Spanned, Field, Lit, Meta, MetaNameValue, Visibility};
+
+pub struct GenParams {
+    pub mode: GenMode,
+    pub global_attr: Option<Meta>,
+}
+
+#[derive(PartialEq, Eq, Copy, Clone)]
+pub enum GenMode {
+    Get,
+    GetCopy,
+    Set,
+    GetMut,
+}
+
+impl GenMode {
+    pub fn name(self) -> &'static str {
+        match self {
+            Get => "get",
+            GetCopy => "get_copy",
+            Set => "set",
+            GetMut => "get_mut",
+        }
+    }
+
+    pub fn prefix(self) -> &'static str {
+        match self {
+            Get | GetCopy | GetMut => "",
+            Set => "set_",
+        }
+    }
+
+    pub fn suffix(self) -> &'static str {
+        match self {
+            Get | GetCopy | Set => "",
+            GetMut => "_mut",
+        }
+    }
+
+    fn is_get(self) -> bool {
+        match self {
+            GenMode::Get | GenMode::GetCopy | GenMode::GetMut => true,
+            GenMode::Set => false,
+        }
+    }
+}
+
+pub fn parse_visibility(attr: Option<&Meta>, meta_name: &str) -> Option<Visibility> {
+    match attr {
+        // `#[get = "pub"]` or `#[set = "pub"]`
+        Some(Meta::NameValue(MetaNameValue {
+            lit: Lit::Str(ref s),
+            path,
+            ..
+        })) => {
+            if path.is_ident(meta_name) {
+                s.value().split(' ').find(|v| *v != "with_prefix").map(|v| {
+                    syn::parse_str(v)
+                        .map_err(|e| syn::Error::new(s.span(), e))
+                        .expect_or_abort("invalid visibility found")
+                })
+            } else {
+                None
+            }
+        }
+        _ => None,
+    }
+}
+
+/// Some users want legacy/compatability.
+/// (Getters are often prefixed with `get_`)
+fn has_prefix_attr(f: &Field, params: &GenParams) -> bool {
+    let inner = f
+        .attrs
+        .iter()
+        .filter_map(|v| parse_attr(v, params.mode))
+        .filter(|meta| {
+            ["get", "get_copy"]
+                .iter()
+                .any(|ident| meta.path().is_ident(ident))
+        })
+        .last();
+
+    // Check it the attr includes `with_prefix`
+    let wants_prefix = |possible_meta: &Option<Meta>| -> bool {
+        match possible_meta {
+            Some(Meta::NameValue(meta)) => {
+                if let Lit::Str(lit_str) = &meta.lit {
+                    // Naive tokenization to avoid a possible visibility mod named `with_prefix`.
+                    lit_str.value().split(' ').any(|v| v == "with_prefix")
+                } else {
+                    false
+                }
+            }
+            _ => false,
+        }
+    };
+
+    // `with_prefix` can either be on the local or global attr
+    wants_prefix(&inner) || wants_prefix(&params.global_attr)
+}
+
+pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 {
+    let field_name = field
+        .clone()
+        .ident
+        .unwrap_or_else(|| abort!(field.span(), "Expected the field to have a name"));
+
+    let fn_name = if !has_prefix_attr(field, params)
+        && (params.mode.is_get())
+        && params.mode.suffix().is_empty()
+        && field_name.to_string().starts_with("r#")
+    {
+        field_name.clone()
+    } else {
+        Ident::new(
+            &format!(
+                "{}{}{}{}",
+                if has_prefix_attr(field, params) && (params.mode.is_get()) {
+                    "get_"
+                } else {
+                    ""
+                },
+                params.mode.prefix(),
+                field_name.unraw(),
+                params.mode.suffix()
+            ),
+            Span::call_site(),
+        )
+    };
+    let ty = field.ty.clone();
+
+    let doc = field.attrs.iter().filter(|v| {
+        v.parse_meta()
+            .map(|meta| meta.path().is_ident("doc"))
+            .unwrap_or(false)
+    });
+
+    let attr = field
+        .attrs
+        .iter()
+        .filter_map(|v| parse_attr(v, params.mode))
+        .last()
+        .or_else(|| params.global_attr.clone());
+
+    let visibility = parse_visibility(attr.as_ref(), params.mode.name());
+    match attr {
+        // Generate nothing for skipped field.
+        Some(meta) if meta.path().is_ident("skip") => quote! {},
+        Some(_) => match params.mode {
+            GenMode::Get => {
+                quote! {
+                    #(#doc)*
+                    #[inline(always)]
+                    #visibility fn #fn_name(&self) -> &#ty {
+                        &self.#field_name
+                    }
+                }
+            }
+            GenMode::GetCopy => {
+                quote! {
+                    #(#doc)*
+                    #[inline(always)]
+                    #visibility fn #fn_name(&self) -> #ty {
+                        self.#field_name
+                    }
+                }
+            }
+            GenMode::Set => {
+                quote! {
+                    #(#doc)*
+                    #[inline(always)]
+                    #visibility fn #fn_name(&mut self, val: #ty) -> &mut Self {
+                        self.#field_name = val;
+                        self
+                    }
+                }
+            }
+            GenMode::GetMut => {
+                quote! {
+                    #(#doc)*
+                    #[inline(always)]
+                    #visibility fn #fn_name(&mut self) -> &mut #ty {
+                        &mut self.#field_name
+                    }
+                }
+            }
+        },
+        // Don't need to do anything.
+        None => quote! {},
+    }
+}
diff --git a/src/rust/getset/src/lib.rs b/src/rust/getset/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9ab860177cbfdf4ed035eaa5f9562991b4497e96
--- /dev/null
+++ b/src/rust/getset/src/lib.rs
@@ -0,0 +1,336 @@
+/*!
+Getset, we're ready to go!
+
+A procedural macro for generating the most basic getters and setters on fields.
+
+Getters are generated as `fn field(&self) -> &type`, while setters are generated as `fn field(&mut self, val: type)`.
+
+These macros are not intended to be used on fields which require custom logic inside of their setters and getters. Just write your own in that case!
+
+```rust
+use getset::{CopyGetters, Getters, MutGetters, Setters};
+
+#[derive(Getters, Setters, MutGetters, CopyGetters, Default)]
+pub struct Foo<T>
+where
+    T: Copy + Clone + Default,
+{
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[getset(get, set, get_mut)]
+    private: T,
+
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[getset(get_copy = "pub", set = "pub", get_mut = "pub")]
+    public: T,
+}
+
+let mut foo = Foo::default();
+foo.set_private(1);
+(*foo.private_mut()) += 1;
+assert_eq!(*foo.private(), 2);
+```
+
+You can use `cargo-expand` to generate the output. Here are the functions that the above generates (Replicate with `cargo expand --example simple`):
+
+```rust,ignore
+use getset::{Getters, MutGetters, CopyGetters, Setters};
+pub struct Foo<T>
+where
+    T: Copy + Clone + Default,
+{
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[getset(get, get, get_mut)]
+    private: T,
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[getset(get_copy = "pub", set = "pub", get_mut = "pub")]
+    public: T,
+}
+impl<T> Foo<T>
+where
+    T: Copy + Clone + Default,
+{
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[inline(always)]
+    fn private(&self) -> &T {
+        &self.private
+    }
+}
+impl<T> Foo<T>
+where
+    T: Copy + Clone + Default,
+{
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[inline(always)]
+    pub fn set_public(&mut self, val: T) -> &mut Self {
+        self.public = val;
+        self
+    }
+}
+impl<T> Foo<T>
+where
+    T: Copy + Clone + Default,
+{
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[inline(always)]
+    fn private_mut(&mut self) -> &mut T {
+        &mut self.private
+    }
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[inline(always)]
+    pub fn public_mut(&mut self) -> &mut T {
+        &mut self.public
+    }
+}
+impl<T> Foo<T>
+where
+    T: Copy + Clone + Default,
+{
+    /// Doc comments are supported!
+    /// Multiline, even.
+    #[inline(always)]
+    pub fn public(&self) -> T {
+        self.public
+    }
+}
+```
+
+Attributes can be set on struct level for all fields in struct as well. Field level attributes take
+precedence.
+
+```rust
+mod submodule {
+    use getset::{Getters, MutGetters, CopyGetters, Setters};
+    #[derive(Getters, CopyGetters, Default)]
+    #[getset(get_copy = "pub")] // By default add a pub getting for all fields.
+    pub struct Foo {
+        public: i32,
+        #[getset(get_copy)] // Override as private
+        private: i32,
+    }
+    fn demo() {
+        let mut foo = Foo::default();
+        foo.private();
+    }
+}
+
+let mut foo = submodule::Foo::default();
+foo.public();
+```
+
+For some purposes, it's useful to have the `get_` prefix on the getters for
+either legacy of compatibility reasons. It is done with `with_prefix`.
+
+```rust
+use getset::{Getters, MutGetters, CopyGetters, Setters};
+
+#[derive(Getters, Default)]
+pub struct Foo {
+    #[getset(get = "pub with_prefix")]
+    field: bool,
+}
+
+
+let mut foo = Foo::default();
+let val = foo.get_field();
+```
+
+Skipping setters and getters generation for a field when struct level attribute is used
+is possible with `#[getset(skip)]`.
+
+```rust
+use getset::{CopyGetters, Setters};
+
+#[derive(CopyGetters, Setters)]
+#[getset(get_copy, set)]
+pub struct Foo {
+    // If the field was not skipped, the compiler would complain about moving
+    // a non-copyable type in copy getter.
+    #[getset(skip)]
+    skipped: String,
+
+    field1: usize,
+    field2: usize,
+}
+
+impl Foo {
+    // It is possible to write getters and setters manually,
+    // possibly with a custom logic.
+    fn skipped(&self) -> &str {
+        &self.skipped
+    }
+
+    fn set_skipped(&mut self, val: &str) -> &mut Self {
+        self.skipped = val.to_string();
+        self
+    }
+}
+```
+*/
+
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use proc_macro_error::{abort, abort_call_site, proc_macro_error, ResultExt};
+use quote::quote;
+use syn::{spanned::Spanned, DataStruct, DeriveInput, Meta};
+
+mod generate;
+use crate::generate::{GenMode, GenParams};
+
+#[proc_macro_derive(Getters, attributes(get, with_prefix, getset))]
+#[proc_macro_error]
+pub fn getters(input: TokenStream) -> TokenStream {
+    // Parse the string representation
+    let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters");
+    let params = GenParams {
+        mode: GenMode::Get,
+        global_attr: parse_global_attr(&ast.attrs, GenMode::Get),
+    };
+
+    // Build the impl
+    let gen = produce(&ast, &params);
+
+    // Return the generated impl
+    gen.into()
+}
+
+#[proc_macro_derive(CopyGetters, attributes(get_copy, with_prefix, getset))]
+#[proc_macro_error]
+pub fn copy_getters(input: TokenStream) -> TokenStream {
+    // Parse the string representation
+    let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters");
+    let params = GenParams {
+        mode: GenMode::GetCopy,
+        global_attr: parse_global_attr(&ast.attrs, GenMode::GetCopy),
+    };
+
+    // Build the impl
+    let gen = produce(&ast, &params);
+
+    // Return the generated impl
+    gen.into()
+}
+
+#[proc_macro_derive(MutGetters, attributes(get_mut, getset))]
+#[proc_macro_error]
+pub fn mut_getters(input: TokenStream) -> TokenStream {
+    // Parse the string representation
+    let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters");
+    let params = GenParams {
+        mode: GenMode::GetMut,
+        global_attr: parse_global_attr(&ast.attrs, GenMode::GetMut),
+    };
+
+    // Build the impl
+    let gen = produce(&ast, &params);
+    // Return the generated impl
+    gen.into()
+}
+
+#[proc_macro_derive(Setters, attributes(set, getset))]
+#[proc_macro_error]
+pub fn setters(input: TokenStream) -> TokenStream {
+    // Parse the string representation
+    let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for setters");
+    let params = GenParams {
+        mode: GenMode::Set,
+        global_attr: parse_global_attr(&ast.attrs, GenMode::Set),
+    };
+
+    // Build the impl
+    let gen = produce(&ast, &params);
+
+    // Return the generated impl
+    gen.into()
+}
+
+fn parse_global_attr(attrs: &[syn::Attribute], mode: GenMode) -> Option<Meta> {
+    attrs
+        .iter()
+        .filter_map(|v| parse_attr(v, mode)) // non "meta" attributes are not our concern
+        .last()
+}
+
+fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option<Meta> {
+    use syn::{punctuated::Punctuated, Token};
+
+    if attr.path.is_ident("getset") {
+        let (last, skip, mut collected) = attr
+            .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
+            .unwrap_or_abort()
+            .into_iter()
+            .inspect(|meta| {
+                if !(meta.path().is_ident("get")
+                    || meta.path().is_ident("get_copy")
+                    || meta.path().is_ident("get_mut")
+                    || meta.path().is_ident("set")
+                    || meta.path().is_ident("skip"))
+                {
+                    abort!(meta.path().span(), "unknown setter or getter")
+                }
+            })
+            .fold(
+                (None, None, Vec::new()),
+                |(last, skip, mut collected), meta| {
+                    if meta.path().is_ident(mode.name()) {
+                        (Some(meta), skip, collected)
+                    } else if meta.path().is_ident("skip") {
+                        (last, Some(meta), collected)
+                    } else {
+                        // Store inapplicable item for potential error message
+                        // if used with skip.
+                        collected.push(meta);
+                        (last, skip, collected)
+                    }
+                },
+            );
+
+        if skip.is_some() {
+            // Check if there is any setter or getter used with skip, which is
+            // forbidden.
+            if last.is_none() && collected.is_empty() {
+                skip
+            } else {
+                abort!(
+                    last.or_else(|| collected.pop()).unwrap().path().span(),
+                    "use of setters and getters with skip is invalid"
+                );
+            }
+        } else {
+            // If skip is not used, return the last occurrence of matching
+            // setter/getter, if there is any.
+            last
+        }
+    } else {
+        attr.parse_meta()
+            .ok()
+            .filter(|meta| meta.path().is_ident(mode.name()))
+    }
+}
+
+fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 {
+    let name = &ast.ident;
+    let generics = &ast.generics;
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+    // Is it a struct?
+    if let syn::Data::Struct(DataStruct { ref fields, .. }) = ast.data {
+        let generated = fields.iter().map(|f| generate::implement(f, params));
+
+        quote! {
+            impl #impl_generics #name #ty_generics #where_clause {
+                #(#generated)*
+            }
+        }
+    } else {
+        // Nope. This is an Enum. We cannot handle these!
+        abort_call_site!("#[derive(Getters)] is only defined for structs, not for enums!");
+    }
+}
diff --git a/src/rust/getset/tests/copy_getters.rs b/src/rust/getset/tests/copy_getters.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c43f7c2665639a2459151337662e573995eac18a
--- /dev/null
+++ b/src/rust/getset/tests/copy_getters.rs
@@ -0,0 +1,149 @@
+#[macro_use]
+extern crate getset;
+
+use crate::submodule::other::{Generic, Plain, Where};
+
+// For testing `pub(super)`
+mod submodule {
+    // For testing `pub(super::other)`
+    pub mod other {
+        #[derive(CopyGetters)]
+        #[get_copy]
+        pub struct Plain {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: usize,
+
+            /// A doc comment.
+            #[get_copy = "pub"]
+            public_accessible: usize,
+            // /// A doc comment.
+            // #[get_copy = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_copy = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_copy = "pub(super::other)"]
+            // scope_accessible: usize,
+
+            // Prefixed getter.
+            #[get_copy = "with_prefix"]
+            private_prefixed: usize,
+
+            // Prefixed getter.
+            #[get_copy = "pub with_prefix"]
+            public_prefixed: usize,
+        }
+
+        impl Default for Plain {
+            fn default() -> Plain {
+                Plain {
+                    private_accessible: 17,
+                    public_accessible: 18,
+                    private_prefixed: 19,
+                    public_prefixed: 20,
+                }
+            }
+        }
+
+        #[derive(CopyGetters, Default)]
+        #[get_copy]
+        pub struct Generic<T: Copy + Clone + Default> {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[get_copy = "pub"]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[get_copy = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_copy = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_copy = "pub(super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[derive(CopyGetters, Getters, Default)]
+        #[get_copy]
+        pub struct Where<T>
+        where
+            T: Copy + Clone + Default,
+        {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[get_copy = "pub"]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[get_copy = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_copy = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_copy = "pub(super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[test]
+        fn test_plain() {
+            let val = Plain::default();
+            val.private_accessible();
+        }
+
+        #[test]
+        fn test_generic() {
+            let val = Generic::<usize>::default();
+            val.private_accessible();
+        }
+
+        #[test]
+        fn test_where() {
+            let val = Where::<usize>::default();
+            val.private_accessible();
+        }
+
+        #[test]
+        fn test_prefixed_plain() {
+            let val = Plain::default();
+            assert_eq!(19, val.get_private_prefixed());
+        }
+    }
+}
+
+#[test]
+fn test_plain() {
+    let val = Plain::default();
+    assert_eq!(18, val.public_accessible());
+}
+
+#[test]
+fn test_generic() {
+    let val = Generic::<usize>::default();
+    assert_eq!(usize::default(), val.public_accessible());
+}
+
+#[test]
+fn test_where() {
+    let val = Where::<usize>::default();
+    assert_eq!(usize::default(), val.public_accessible());
+}
+
+#[test]
+fn test_prefixed_plain() {
+    let val = Plain::default();
+    assert_eq!(20, val.get_public_prefixed());
+}
diff --git a/src/rust/getset/tests/getset.rs b/src/rust/getset/tests/getset.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9985078072de14cdd6adb6307683c63b1605e7bf
--- /dev/null
+++ b/src/rust/getset/tests/getset.rs
@@ -0,0 +1,121 @@
+#[macro_use]
+extern crate getset;
+
+use crate::submodule::other::{Generic, Plain, Where};
+
+// For testing `pub(super)`
+mod submodule {
+    // For testing `pub(in super::other)`
+    pub mod other {
+        #[derive(MutGetters, Getters, Default)]
+        #[getset(get_mut)]
+        pub struct Plain {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: usize,
+
+            /// A doc comment.
+            #[getset(get = "pub", get_mut = "pub")]
+            public_accessible: usize,
+            // /// A doc comment.
+            // #[get_mut = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(in super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[derive(MutGetters, Getters, Default)]
+        #[getset(get_mut)]
+        pub struct Generic<T: Copy + Clone + Default> {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[getset(get = "pub", get_mut = "pub")]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[get_mut = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(in super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[derive(MutGetters, Getters, Default)]
+        #[getset(get_mut)]
+        pub struct Where<T>
+        where
+            T: Copy + Clone + Default,
+        {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[getset(get = "pub", get_mut = "pub")]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[get_mut = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(in super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[test]
+        fn test_plain() {
+            let mut val = Plain::default();
+            (*val.private_accessible_mut()) += 1;
+        }
+
+        #[test]
+        fn test_generic() {
+            let mut val = Generic::<usize>::default();
+            (*val.private_accessible_mut()) += 1;
+        }
+
+        #[test]
+        fn test_where() {
+            let mut val = Where::<usize>::default();
+            (*val.private_accessible_mut()) += 1;
+        }
+    }
+}
+
+#[test]
+fn test_plain() {
+    let mut val = Plain::default();
+    let _ = val.public_accessible();
+    (*val.public_accessible_mut()) += 1;
+}
+
+#[test]
+fn test_generic() {
+    let mut val = Generic::<usize>::default();
+    let _ = val.public_accessible();
+    (*val.public_accessible_mut()) += 1;
+}
+
+#[test]
+fn test_where() {
+    let mut val = Where::<usize>::default();
+    let _ = val.public_accessible();
+    (*val.public_accessible_mut()) += 1;
+}
diff --git a/src/rust/getset/tests/getters.rs b/src/rust/getset/tests/getters.rs
new file mode 100644
index 0000000000000000000000000000000000000000..91debf3f0d30d0c399156627d5b6cb0c7fe38d4f
--- /dev/null
+++ b/src/rust/getset/tests/getters.rs
@@ -0,0 +1,149 @@
+#[macro_use]
+extern crate getset;
+
+use crate::submodule::other::{Generic, Plain, Where};
+
+// For testing `pub(super)`
+mod submodule {
+    // For testing `pub(super::other)`
+    pub mod other {
+        #[derive(Getters)]
+        #[get]
+        pub struct Plain {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: usize,
+
+            /// A doc comment.
+            #[get = "pub"]
+            public_accessible: usize,
+            // /// A doc comment.
+            // #[get = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get = "pub(super::other)"]
+            // scope_accessible: usize,
+
+            // Prefixed getter.
+            #[get = "with_prefix"]
+            private_prefixed: usize,
+
+            // Prefixed getter.
+            #[get = "pub with_prefix"]
+            public_prefixed: usize,
+        }
+
+        impl Default for Plain {
+            fn default() -> Plain {
+                Plain {
+                    private_accessible: 17,
+                    public_accessible: 18,
+                    private_prefixed: 19,
+                    public_prefixed: 20,
+                }
+            }
+        }
+
+        #[derive(Getters, Default)]
+        #[get]
+        pub struct Generic<T: Copy + Clone + Default> {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[get = "pub(crate)"]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[get = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get = "pub(super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[derive(Getters, Default)]
+        #[get]
+        pub struct Where<T>
+        where
+            T: Copy + Clone + Default,
+        {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[get = "pub"]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[get = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get = "pub(super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[test]
+        fn test_plain() {
+            let val = Plain::default();
+            val.private_accessible();
+        }
+
+        #[test]
+        fn test_generic() {
+            let val = Generic::<usize>::default();
+            val.private_accessible();
+        }
+
+        #[test]
+        fn test_where() {
+            let val = Where::<usize>::default();
+            val.private_accessible();
+        }
+
+        #[test]
+        fn test_prefixed_plain() {
+            let val = Plain::default();
+            assert_eq!(19, *val.get_private_prefixed());
+        }
+    }
+}
+
+#[test]
+fn test_plain() {
+    let val = Plain::default();
+    assert_eq!(18, *val.public_accessible());
+}
+
+#[test]
+fn test_generic() {
+    let val = Generic::<usize>::default();
+    assert_eq!(usize::default(), *val.public_accessible());
+}
+
+#[test]
+fn test_where() {
+    let val = Where::<usize>::default();
+    assert_eq!(usize::default(), *val.public_accessible());
+}
+
+#[test]
+fn test_prefixed_plain() {
+    let val = Plain::default();
+    assert_eq!(20, *val.get_public_prefixed());
+}
diff --git a/src/rust/getset/tests/mut_getters.rs b/src/rust/getset/tests/mut_getters.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c536f5c34cdb7c8f44f32e8d0e460e6d9140fa62
--- /dev/null
+++ b/src/rust/getset/tests/mut_getters.rs
@@ -0,0 +1,118 @@
+#[macro_use]
+extern crate getset;
+
+use crate::submodule::other::{Generic, Plain, Where};
+
+// For testing `pub(super)`
+mod submodule {
+    // For testing `pub(in super::other)`
+    pub mod other {
+        #[derive(MutGetters, Default)]
+        #[get_mut]
+        pub struct Plain {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: usize,
+
+            /// A doc comment.
+            #[get_mut = "pub"]
+            public_accessible: usize,
+            // /// A doc comment.
+            // #[get_mut = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(in super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[derive(MutGetters, Default)]
+        #[get_mut]
+        pub struct Generic<T: Copy + Clone + Default> {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[get_mut = "pub"]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[get_mut = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(in super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[derive(MutGetters, Default)]
+        #[get_mut]
+        pub struct Where<T>
+        where
+            T: Copy + Clone + Default,
+        {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[get_mut = "pub"]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[get_mut = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[get_mut = "pub(in super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[test]
+        fn test_plain() {
+            let mut val = Plain::default();
+            (*val.private_accessible_mut()) += 1;
+        }
+
+        #[test]
+        fn test_generic() {
+            let mut val = Generic::<usize>::default();
+            (*val.private_accessible_mut()) += 1;
+        }
+
+        #[test]
+        fn test_where() {
+            let mut val = Where::<usize>::default();
+            (*val.private_accessible_mut()) += 1;
+        }
+    }
+}
+
+#[test]
+fn test_plain() {
+    let mut val = Plain::default();
+    (*val.public_accessible_mut()) += 1;
+}
+
+#[test]
+fn test_generic() {
+    let mut val = Generic::<usize>::default();
+    (*val.public_accessible_mut()) += 1;
+}
+
+#[test]
+fn test_where() {
+    let mut val = Where::<usize>::default();
+    (*val.public_accessible_mut()) += 1;
+}
diff --git a/src/rust/getset/tests/raw_identifiers.rs b/src/rust/getset/tests/raw_identifiers.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7e67c4240b67113a8b69890f30f963794222c031
--- /dev/null
+++ b/src/rust/getset/tests/raw_identifiers.rs
@@ -0,0 +1,57 @@
+#[macro_use]
+extern crate getset;
+
+#[derive(CopyGetters, Default, Getters, MutGetters, Setters)]
+struct RawIdentifiers {
+    #[get]
+    r#type: usize,
+    #[get_copy]
+    r#move: usize,
+    #[get_mut]
+    r#union: usize,
+    #[set]
+    r#enum: usize,
+    #[get = "with_prefix"]
+    r#const: usize,
+    #[get_copy = "with_prefix"]
+    r#if: usize,
+    // Ensure having no gen mode doesn't break things.
+    #[allow(dead_code)]
+    r#loop: usize,
+}
+
+#[test]
+fn test_get() {
+    let val = RawIdentifiers::default();
+    let _ = val.r#type();
+}
+
+#[test]
+fn test_get_copy() {
+    let val = RawIdentifiers::default();
+    let _ = val.r#move();
+}
+
+#[test]
+fn test_get_mut() {
+    let mut val = RawIdentifiers::default();
+    let _ = val.union_mut();
+}
+
+#[test]
+fn test_set() {
+    let mut val = RawIdentifiers::default();
+    val.set_enum(42);
+}
+
+#[test]
+fn test_get_with_prefix() {
+    let val = RawIdentifiers::default();
+    let _ = val.get_const();
+}
+
+#[test]
+fn test_get_copy_with_prefix() {
+    let val = RawIdentifiers::default();
+    let _ = val.get_if();
+}
diff --git a/src/rust/getset/tests/setters.rs b/src/rust/getset/tests/setters.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ba5998d2e8db8426fa01b0977d404224c32eb39f
--- /dev/null
+++ b/src/rust/getset/tests/setters.rs
@@ -0,0 +1,129 @@
+#[macro_use]
+extern crate getset;
+
+use crate::submodule::other::{Generic, Plain, Where};
+
+// For testing `pub(super)`
+mod submodule {
+    // For testing `pub(in super::other)`
+    pub mod other {
+        #[derive(Setters, Default)]
+        #[set]
+        pub struct Plain {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: usize,
+
+            /// A doc comment.
+            #[set = "pub"]
+            public_accessible: usize,
+
+            /// This field is used for testing chaining.
+            #[set = "pub"]
+            second_public_accessible: bool,
+            // /// A doc comment.
+            // #[set = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[set = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[set = "pub(in super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[derive(Setters, Default)]
+        #[set]
+        pub struct Generic<T: Copy + Clone + Default> {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[set = "pub"]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[set = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[set = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[set = "pub(in super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[derive(Setters, Default)]
+        #[set]
+        pub struct Where<T>
+        where
+            T: Copy + Clone + Default,
+        {
+            /// A doc comment.
+            /// Multiple lines, even.
+            private_accessible: T,
+
+            /// A doc comment.
+            #[set = "pub"]
+            public_accessible: T,
+            // /// A doc comment.
+            // #[set = "pub(crate)"]
+            // crate_accessible: usize,
+
+            // /// A doc comment.
+            // #[set = "pub(super)"]
+            // super_accessible: usize,
+
+            // /// A doc comment.
+            // #[set = "pub(in super::other)"]
+            // scope_accessible: usize,
+        }
+
+        #[test]
+        fn test_plain() {
+            let mut val = Plain::default();
+            val.set_private_accessible(1);
+        }
+
+        #[test]
+        fn test_generic() {
+            let mut val = Generic::default();
+            val.set_private_accessible(1);
+        }
+
+        #[test]
+        fn test_where() {
+            let mut val = Where::default();
+            val.set_private_accessible(1);
+        }
+    }
+}
+
+#[test]
+fn test_plain() {
+    let mut val = Plain::default();
+    val.set_public_accessible(1);
+}
+
+#[test]
+fn test_generic() {
+    let mut val = Generic::default();
+    val.set_public_accessible(1);
+}
+
+#[test]
+fn test_where() {
+    let mut val = Where::default();
+    val.set_public_accessible(1);
+}
+
+#[test]
+fn test_chaining() {
+    let mut val = Plain::default();
+    val.set_public_accessible(1)
+        .set_second_public_accessible(true);
+}
diff --git a/src/rust/getset/tests/skip.rs b/src/rust/getset/tests/skip.rs
new file mode 100644
index 0000000000000000000000000000000000000000..93f40e4f72ae2220b0364acd72f4e6d3e68fe551
--- /dev/null
+++ b/src/rust/getset/tests/skip.rs
@@ -0,0 +1,50 @@
+#[macro_use]
+extern crate getset;
+
+#[derive(CopyGetters, Setters)]
+#[getset(get_copy, set)]
+pub struct Plain {
+    // If the field was not skipped, the compiler would complain about moving a
+    // non-copyable type.
+    #[getset(skip)]
+    non_copyable: String,
+
+    copyable: usize,
+    // Invalid use of skip -- compilation error.
+    // #[getset(skip, get_copy)]
+    // non_copyable2: String,
+
+    // Invalid use of skip -- compilation error.
+    // #[getset(get_copy, skip)]
+    // non_copyable2: String,
+}
+
+impl Plain {
+    fn custom_non_copyable(&self) -> &str {
+        &self.non_copyable
+    }
+
+    // If the field was not skipped, the compiler would complain about duplicate
+    // definitions of `set_non_copyable`.
+    fn set_non_copyable(&mut self, val: String) -> &mut Self {
+        self.non_copyable = val;
+        self
+    }
+}
+
+impl Default for Plain {
+    fn default() -> Self {
+        Plain {
+            non_copyable: "foo".to_string(),
+            copyable: 3,
+        }
+    }
+}
+
+#[test]
+fn test_plain() {
+    let mut val = Plain::default();
+    val.copyable();
+    val.custom_non_copyable();
+    val.set_non_copyable("bar".to_string());
+}