diff --git a/crates/hir-def/src/data/adt.rs b/crates/hir-def/src/data/adt.rs
index 8ea79406531d..e470fe689b91 100644
--- a/crates/hir-def/src/data/adt.rs
+++ b/crates/hir-def/src/data/adt.rs
@@ -171,6 +171,7 @@ pub struct FieldData {
     pub name: Name,
     pub type_ref: TypeRefId,
     pub visibility: RawVisibility,
+    pub is_unsafe: bool,
 }
 
 fn repr_from_value(
@@ -329,14 +330,14 @@ impl EnumVariantData {
 impl VariantData {
     pub fn fields(&self) -> &Arena<FieldData> {
         const EMPTY: &Arena<FieldData> = &Arena::new();
-        match &self {
+        match self {
             VariantData::Record { fields, .. } | VariantData::Tuple { fields, .. } => fields,
             _ => EMPTY,
         }
     }
 
     pub fn types_map(&self) -> &TypesMap {
-        match &self {
+        match self {
             VariantData::Record { types_map, .. } | VariantData::Tuple { types_map, .. } => {
                 types_map
             }
@@ -405,5 +406,6 @@ fn lower_field(
         name: field.name.clone(),
         type_ref: field.type_ref,
         visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(),
+        is_unsafe: field.is_unsafe,
     }
 }
diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs
index 2debbb1ee4e3..80d666a0aba1 100644
--- a/crates/hir-def/src/item_tree.rs
+++ b/crates/hir-def/src/item_tree.rs
@@ -1032,6 +1032,7 @@ pub struct Field {
     pub name: Name,
     pub type_ref: TypeRefId,
     pub visibility: RawVisibilityId,
+    pub is_unsafe: bool,
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs
index 686682142052..5389ba49c577 100644
--- a/crates/hir-def/src/item_tree/lower.rs
+++ b/crates/hir-def/src/item_tree/lower.rs
@@ -320,7 +320,7 @@ impl<'a> Ctx<'a> {
         let visibility = self.lower_visibility(field);
         let type_ref = TypeRef::from_ast_opt(body_ctx, field.ty());
 
-        Field { name, type_ref, visibility }
+        Field { name, type_ref, visibility, is_unsafe: field.unsafe_token().is_some() }
     }
 
     fn lower_tuple_field(
@@ -332,7 +332,7 @@ impl<'a> Ctx<'a> {
         let name = Name::new_tuple_field(idx);
         let visibility = self.lower_visibility(field);
         let type_ref = TypeRef::from_ast_opt(body_ctx, field.ty());
-        Field { name, type_ref, visibility }
+        Field { name, type_ref, visibility, is_unsafe: false }
     }
 
     fn lower_union(&mut self, union: &ast::Union) -> Option<FileItemTreeId<Union>> {
diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs
index e666b1ea6bc2..f44a000ac1ba 100644
--- a/crates/hir-def/src/item_tree/pretty.rs
+++ b/crates/hir-def/src/item_tree/pretty.rs
@@ -135,12 +135,17 @@ impl Printer<'_> {
                 self.whitespace();
                 w!(self, "{{");
                 self.indented(|this| {
-                    for (idx, Field { name, type_ref, visibility }) in fields.iter().enumerate() {
+                    for (idx, Field { name, type_ref, visibility, is_unsafe }) in
+                        fields.iter().enumerate()
+                    {
                         this.print_attrs_of(
                             AttrOwner::Field(parent, Idx::from_raw(RawIdx::from(idx as u32))),
                             "\n",
                         );
                         this.print_visibility(*visibility);
+                        if *is_unsafe {
+                            w!(this, "unsafe ");
+                        }
                         w!(this, "{}: ", name.display(self.db.upcast(), edition));
                         this.print_type_ref(*type_ref, map);
                         wln!(this, ",");
@@ -151,12 +156,17 @@ impl Printer<'_> {
             FieldsShape::Tuple => {
                 w!(self, "(");
                 self.indented(|this| {
-                    for (idx, Field { name, type_ref, visibility }) in fields.iter().enumerate() {
+                    for (idx, Field { name, type_ref, visibility, is_unsafe }) in
+                        fields.iter().enumerate()
+                    {
                         this.print_attrs_of(
                             AttrOwner::Field(parent, Idx::from_raw(RawIdx::from(idx as u32))),
                             "\n",
                         );
                         this.print_visibility(*visibility);
+                        if *is_unsafe {
+                            w!(this, "unsafe ");
+                        }
                         w!(this, "{}: ", name.display(self.db.upcast(), edition));
                         this.print_type_ref(*type_ref, map);
                         wln!(this, ",");
diff --git a/crates/parser/src/grammar/items/adt.rs b/crates/parser/src/grammar/items/adt.rs
index 9a16c9db6daf..a37569614028 100644
--- a/crates/parser/src/grammar/items/adt.rs
+++ b/crates/parser/src/grammar/items/adt.rs
@@ -107,7 +107,7 @@ pub(crate) fn variant_list(p: &mut Parser<'_>) {
 }
 
 // test record_field_list
-// struct S { a: i32, b: f32 }
+// struct S { a: i32, b: f32, unsafe c: u8 }
 pub(crate) fn record_field_list(p: &mut Parser<'_>) {
     assert!(p.at(T!['{']));
     let m = p.start();
@@ -131,6 +131,7 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) {
         // struct S { #[attr] f: f32 }
         attributes::outer_attrs(p);
         opt_visibility(p, false);
+        p.eat(T![unsafe]);
         if p.at(IDENT) {
             name(p);
             p.expect(T![:]);
diff --git a/crates/parser/test_data/parser/inline/ok/record_field_list.rast b/crates/parser/test_data/parser/inline/ok/record_field_list.rast
index 065d7e7e81f2..07686f509c1b 100644
--- a/crates/parser/test_data/parser/inline/ok/record_field_list.rast
+++ b/crates/parser/test_data/parser/inline/ok/record_field_list.rast
@@ -30,6 +30,20 @@ SOURCE_FILE
             PATH_SEGMENT
               NAME_REF
                 IDENT "f32"
+      COMMA ","
+      WHITESPACE " "
+      RECORD_FIELD
+        UNSAFE_KW "unsafe"
+        WHITESPACE " "
+        NAME
+          IDENT "c"
+        COLON ":"
+        WHITESPACE " "
+        PATH_TYPE
+          PATH
+            PATH_SEGMENT
+              NAME_REF
+                IDENT "u8"
       WHITESPACE " "
       R_CURLY "}"
   WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/record_field_list.rs b/crates/parser/test_data/parser/inline/ok/record_field_list.rs
index a3bd7787db77..1f4612f53913 100644
--- a/crates/parser/test_data/parser/inline/ok/record_field_list.rs
+++ b/crates/parser/test_data/parser/inline/ok/record_field_list.rs
@@ -1 +1 @@
-struct S { a: i32, b: f32 }
+struct S { a: i32, b: f32, unsafe c: u8 }
diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram
index 70a91af6c479..673334bd2251 100644
--- a/crates/syntax/rust.ungram
+++ b/crates/syntax/rust.ungram
@@ -240,7 +240,7 @@ RecordFieldList =
  '{' fields:(RecordField (',' RecordField)* ','?)? '}'
 
 RecordField =
-  Attr* Visibility?
+  Attr* Visibility? 'unsafe'?
   Name ':' Type ('=' Expr)?
 
 TupleFieldList =
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index 638b9615ea33..fd23cdccd572 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -1337,6 +1337,8 @@ impl RecordField {
     pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) }
     #[inline]
     pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
+    #[inline]
+    pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
 }
 pub struct RecordFieldList {
     pub(crate) syntax: SyntaxNode,