diff --git a/gcc/rust/ast/rust-ast-collector.cc b/gcc/rust/ast/rust-ast-collector.cc index 12f3e106c8f0..389be0644984 100644 --- a/gcc/rust/ast/rust-ast-collector.cc +++ b/gcc/rust/ast/rust-ast-collector.cc @@ -429,6 +429,9 @@ TokenCollector::visit (Token &tok) case RAW_STRING_LITERAL: push (Rust::Token::make_raw_string (tok.get_locus (), std::move (data))); break; + case C_STRING_LITERAL: + push (Rust::Token::make_c_string (tok.get_locus (), std::move (data))); + break; case INNER_DOC_COMMENT: push (Rust::Token::make_inner_doc_comment (tok.get_locus (), std::move (data))); @@ -863,6 +866,9 @@ TokenCollector::visit (Literal &lit, location_t locus) case Literal::LitType::RAW_STRING: push (Rust::Token::make_raw_string (locus, std::move (value))); break; + case Literal::LitType::C_STRING: + push (Rust::Token::make_c_string (locus, std::move (value))); + break; case Literal::LitType::INT: { auto val_len = value.length (); diff --git a/gcc/rust/ast/rust-ast.cc b/gcc/rust/ast/rust-ast.cc index 5880ea92fd3e..872dff315763 100644 --- a/gcc/rust/ast/rust-ast.cc +++ b/gcc/rust/ast/rust-ast.cc @@ -3620,6 +3620,7 @@ AttributeParser::parse_meta_item_inner () case BYTE_CHAR_LITERAL: case BYTE_STRING_LITERAL: case RAW_STRING_LITERAL: + case C_STRING_LITERAL: case INT_LITERAL: case FLOAT_LITERAL: case TRUE_LITERAL: diff --git a/gcc/rust/ast/rust-ast.h b/gcc/rust/ast/rust-ast.h index f6b704507f70..8afa04bebf41 100644 --- a/gcc/rust/ast/rust-ast.h +++ b/gcc/rust/ast/rust-ast.h @@ -209,6 +209,7 @@ class Token : public TokenTree, public MacroMatch case STRING_LITERAL: case BYTE_STRING_LITERAL: case RAW_STRING_LITERAL: + case C_STRING_LITERAL: return true; default: return false; @@ -272,6 +273,7 @@ struct Literal BYTE, BYTE_STRING, RAW_STRING, + C_STRING, INT, FLOAT, BOOL, diff --git a/gcc/rust/backend/rust-compile-expr.cc b/gcc/rust/backend/rust-compile-expr.cc index 36c088e8eaf9..c216c1ef5d5a 100644 --- a/gcc/rust/backend/rust-compile-expr.cc +++ b/gcc/rust/backend/rust-compile-expr.cc @@ -717,8 +717,23 @@ CompileExpr::visit (HIR::FieldAccessExpr &expr) nullptr, &field_index); rust_assert (ok); - tree indirect = indirect_expression (receiver_ref, expr.get_locus ()); - receiver_ref = indirect; + // TODO this check is only used for CStr, test again when we support + // compilation of #[repr(transparent)] structs + if (RS_DST_FLAG_P (TREE_TYPE (receiver_ref))) + { + const TyTy::StructFieldType *field + = variant->get_field_at_index (field_index); + tree field_type + = TyTyResolveCompile::compile (ctx, field->get_field_type ()); + translated = fold_build1_loc (expr.get_locus (), VIEW_CONVERT_EXPR, + field_type, receiver_ref); + return; + } + else + { + tree indirect = indirect_expression (receiver_ref, expr.get_locus ()); + receiver_ref = indirect; + } } translated = Backend::struct_field_expression (receiver_ref, field_index, @@ -1089,6 +1104,10 @@ CompileExpr::visit (HIR::LiteralExpr &expr) case HIR::Literal::BYTE_STRING: translated = compile_byte_string_literal (expr, tyty); return; + + case HIR::Literal::C_STRING: + translated = compile_c_string_literal (expr, tyty); + return; } } @@ -1902,6 +1921,31 @@ CompileExpr::compile_byte_string_literal (const HIR::LiteralExpr &expr, return address_expression (constructed, expr.get_locus ()); } +tree +CompileExpr::compile_c_string_literal (const HIR::LiteralExpr &expr, + const TyTy::BaseType *tyty) +{ + // Copied from compile_string_literal + tree fat_pointer = TyTyResolveCompile::compile (ctx, tyty); + + rust_assert (expr.get_lit_type () == HIR::Literal::C_STRING); + const auto literal_value = expr.get_literal (); + + auto base = Backend::string_constant_expression (literal_value.as_string ()); + tree data = address_expression (base, expr.get_locus ()); + + TyTy::BaseType *usize = nullptr; + bool ok = ctx->get_tyctx ()->lookup_builtin ("usize", &usize); + rust_assert (ok); + tree type = TyTyResolveCompile::compile (ctx, usize); + + // +1 for null terminator, unlike Rust string literals. + tree size = build_int_cstu (type, literal_value.as_string ().size () + 1); + + return Backend::constructor_expression (fat_pointer, false, {data, size}, -1, + expr.get_locus ()); +} + tree CompileExpr::type_cast_expression (tree type_to_cast_to, tree expr_tree, location_t location) diff --git a/gcc/rust/backend/rust-compile-expr.h b/gcc/rust/backend/rust-compile-expr.h index 63ada9f33a69..532e140c9ccf 100644 --- a/gcc/rust/backend/rust-compile-expr.h +++ b/gcc/rust/backend/rust-compile-expr.h @@ -131,6 +131,9 @@ class CompileExpr : private HIRCompileBase, protected HIR::HIRExpressionVisitor tree compile_byte_string_literal (const HIR::LiteralExpr &expr, const TyTy::BaseType *tyty); + tree compile_c_string_literal (const HIR::LiteralExpr &expr, + const TyTy::BaseType *tyty); + tree type_cast_expression (tree type_to_cast_to, tree expr, location_t locus); tree array_value_expr (location_t expr_locus, diff --git a/gcc/rust/backend/rust-compile-type.cc b/gcc/rust/backend/rust-compile-type.cc index 46fcea88faad..dafec9da653b 100644 --- a/gcc/rust/backend/rust-compile-type.cc +++ b/gcc/rust/backend/rust-compile-type.cc @@ -650,6 +650,7 @@ TyTyResolveCompile::visit (const TyTy::ReferenceType &type) const TyTy::SliceType *slice = nullptr; const TyTy::StrType *str = nullptr; const TyTy::DynamicObjectType *dyn = nullptr; + const TyTy::ADTType *adt = nullptr; if (type.is_dyn_slice_type (&slice)) { tree type_record = create_slice_type_record (*slice); @@ -684,6 +685,28 @@ TyTyResolveCompile::visit (const TyTy::ReferenceType &type) return; } + // Check for CStr, create a specific record for it + else if (type.is_dyn_cstr_type (&adt)) + { + // CStr in core crate is defined as the following: + // + // #[repr(transparent)] + // pub struct CStr { + // inner: [u8] + // } + // + // Reuse the c_char (u8) slice fat-pointer layout + TyTy::BaseType *u8 = nullptr; + ctx->get_tyctx ()->lookup_builtin ("u8", &u8); + // Create a synthetic SliceType over u8 and use that record layout + TyTy::SliceType synthetic_slice (adt->get_ref (), adt->get_ident ().locus, + TyTy::TyVar (u8->get_ref ())); + tree type_record = create_slice_type_record (synthetic_slice); + translated + = Backend::named_type ("&CStr", type_record, adt->get_ident ().locus); + + return; + } tree base_compiled_type = TyTyResolveCompile::compile (ctx, type.get_base (), trait_object_mode); diff --git a/gcc/rust/hir/rust-ast-lower-base.cc b/gcc/rust/hir/rust-ast-lower-base.cc index 6fc4463a9340..a1f84c9f0779 100644 --- a/gcc/rust/hir/rust-ast-lower-base.cc +++ b/gcc/rust/hir/rust-ast-lower-base.cc @@ -1018,6 +1018,9 @@ ASTLoweringBase::lower_literal (const AST::Literal &literal) case AST::Literal::LitType::RAW_STRING: type = HIR::Literal::LitType::STRING; break; + case AST::Literal::LitType::C_STRING: + type = HIR::Literal::LitType::C_STRING; + break; case AST::Literal::LitType::INT: type = HIR::Literal::LitType::INT; break; diff --git a/gcc/rust/hir/tree/rust-hir-literal.h b/gcc/rust/hir/tree/rust-hir-literal.h index b007f800fe7f..7b030340106a 100644 --- a/gcc/rust/hir/tree/rust-hir-literal.h +++ b/gcc/rust/hir/tree/rust-hir-literal.h @@ -33,6 +33,7 @@ struct Literal STRING, BYTE, BYTE_STRING, + C_STRING, INT, FLOAT, BOOL diff --git a/gcc/rust/lang.opt b/gcc/rust/lang.opt index 2fe63fa70935..1d38fec8635a 100644 --- a/gcc/rust/lang.opt +++ b/gcc/rust/lang.opt @@ -237,4 +237,8 @@ frust-unused-check-2.0 Rust Var(flag_unused_check_2_0) Use the new unused variable check implementation. +frust-c-style-string-literals +Rust Var(flag_c_style_string_literals) +Enable parsing and compilation of C-styled string literals. + ; This comment is to ensure we retain the blank line above. diff --git a/gcc/rust/lex/rust-lex.cc b/gcc/rust/lex/rust-lex.cc index 4f135b19a34b..f5f2f4aa60da 100644 --- a/gcc/rust/lex/rust-lex.cc +++ b/gcc/rust/lex/rust-lex.cc @@ -1063,6 +1063,10 @@ Lexer::build_token () return parse_raw_byte_string (loc); } + // C-style strings + else if (current_char == 'c' && peek_input () == '"') + return parse_c_string (loc); + // raw identifiers and raw strings if (current_char == 'r') { @@ -1749,6 +1753,82 @@ Lexer::parse_byte_string (location_t loc) return Token::make_byte_string (loc, std::move (str)); } +// Parses a C-style string. +TokenPtr +Lexer::parse_c_string (location_t loc) +{ + skip_input (); + current_column++; + + // Mostly same code copied from parse_string... + + std::string str; + str.reserve (16); // some sensible default + + current_char = peek_input (); + + const location_t string_begin_locus = get_current_location (); + + while (current_char.value != '"' && !current_char.is_eof ()) + { + if (current_char.value == '\\') + { + int length = 1; + + auto escape_pair = parse_escape ('"'); + current_char = std::get<0> (escape_pair); + + if (current_char == Codepoint (0) && std::get<2> (escape_pair)) + length = std::get<1> (escape_pair) - 1; + else + length += std::get<1> (escape_pair); + + if (current_char != Codepoint (0) || !std::get<2> (escape_pair)) + str += current_char.as_string (); + + current_column += length; + + // FIXME: parse_escape does not update current_char correctly. + current_char = peek_input (); + continue; + } + + current_column++; + if (current_char.value == '\n') + { + current_line++; + current_column = 1; + // tell line_table that new line starts + start_line (current_line, max_column_hint); + } + + str += current_char; + skip_input (); + current_char = peek_input (); + } + + if (current_char.value == '"') + { + current_column++; + + skip_input (); + current_char = peek_input (); + } + else if (current_char.is_eof ()) + { + rust_error_at (string_begin_locus, "unended C string literal"); + return Token::make (END_OF_FILE, get_current_location ()); + } + else + { + rust_unreachable (); + } + + str.shrink_to_fit (); + + return Token::make_c_string (loc, std::move (str)); +} + // Parses a raw byte string. TokenPtr Lexer::parse_raw_byte_string (location_t loc) diff --git a/gcc/rust/lex/rust-lex.h b/gcc/rust/lex/rust-lex.h index 132005a164f1..8d65a6ddf551 100644 --- a/gcc/rust/lex/rust-lex.h +++ b/gcc/rust/lex/rust-lex.h @@ -147,6 +147,7 @@ class Lexer TokenPtr parse_string (location_t loc); TokenPtr maybe_parse_raw_string (location_t loc); TokenPtr parse_raw_string (location_t loc, int initial_hash_count); + TokenPtr parse_c_string (location_t loc); TokenPtr parse_non_decimal_int_literals (location_t loc); TokenPtr parse_decimal_int_or_float (location_t loc); TokenPtr parse_char_or_lifetime (location_t loc); diff --git a/gcc/rust/lex/rust-token.h b/gcc/rust/lex/rust-token.h index f3e2e944100d..670affd72dca 100644 --- a/gcc/rust/lex/rust-token.h +++ b/gcc/rust/lex/rust-token.h @@ -134,6 +134,8 @@ enum PrimitiveCoreType RS_TOKEN (BYTE_STRING_LITERAL, "byte string literal") \ RS_TOKEN (RAW_STRING_LITERAL, "raw string literal") \ RS_TOKEN (BYTE_CHAR_LITERAL, "byte character literal") \ + RS_TOKEN (C_STRING_LITERAL, "C string literal") \ + RS_TOKEN (RAW_C_STRING_LITERAL, "raw C string literal") \ RS_TOKEN (LIFETIME, "lifetime") /* TODO: improve token type */ \ /* Have "interpolated" tokens (whatever that means)? identifer, path, type, \ * pattern, */ \ @@ -422,6 +424,11 @@ class Token return TokenPtr (new Token (RAW_STRING_LITERAL, locus, std::move (str))); } + static TokenPtr make_c_string (location_t locus, std::string str) + { + return TokenPtr (new Token (C_STRING_LITERAL, locus, std::move (str))); + } + // Makes and returns a new TokenPtr of type INNER_DOC_COMMENT. static TokenPtr make_inner_doc_comment (location_t locus, std::string str) { @@ -504,6 +511,7 @@ class Token case BYTE_CHAR_LITERAL: case BYTE_STRING_LITERAL: case RAW_STRING_LITERAL: + case C_STRING_LITERAL: return true; default: return false; diff --git a/gcc/rust/parse/rust-parse-impl-attribute.hxx b/gcc/rust/parse/rust-parse-impl-attribute.hxx index 3db1cd5c69e4..d473c9e2b9b0 100644 --- a/gcc/rust/parse/rust-parse-impl-attribute.hxx +++ b/gcc/rust/parse/rust-parse-impl-attribute.hxx @@ -336,6 +336,9 @@ Parser::parse_attr_input () case BYTE_STRING_LITERAL: lit_type = AST::Literal::BYTE_STRING; break; + case C_STRING_LITERAL: + lit_type = AST::Literal::C_STRING; + break; case RAW_STRING_LITERAL: lit_type = AST::Literal::RAW_STRING; break; diff --git a/gcc/rust/parse/rust-parse-impl-expr.hxx b/gcc/rust/parse/rust-parse-impl-expr.hxx index 887991541631..ac11d3e04a71 100644 --- a/gcc/rust/parse/rust-parse-impl-expr.hxx +++ b/gcc/rust/parse/rust-parse-impl-expr.hxx @@ -341,6 +341,28 @@ Parser::parse_literal_expr (AST::AttrVec outer_attrs) type = AST::Literal::RAW_STRING; literal_value = t->get_str (); lexer.skip_token (); + break; + case C_STRING_LITERAL: + { + if (flag_c_style_string_literals) + { + type = AST::Literal::C_STRING; + literal_value = t->get_str (); + lexer.skip_token (); + } + else + { + add_error ( + Error (t->get_locus (), + "unexpected token %qs when parsing literal expression - " + "C-style string literals require " + "%<-frust-c-style-string-literals%> to be enabled", + t->get_token_description ())); + return tl::unexpected ( + Parse::Error::Node::MALFORMED); + } + } + break; case INT_LITERAL: type = AST::Literal::INT; @@ -2101,6 +2123,23 @@ Parser::null_denotation_not_path ( return std::unique_ptr ( new AST::LiteralExpr (tok->get_str (), AST::Literal::RAW_STRING, tok->get_type_hint (), {}, tok->get_locus ())); + case C_STRING_LITERAL: + if (flag_c_style_string_literals) + { + return std::unique_ptr ( + new AST::LiteralExpr (tok->get_str (), AST::Literal::C_STRING, + tok->get_type_hint (), {}, + tok->get_locus ())); + } + else + { + Error error (tok->get_locus (), + "C-style string literals require " + "%<-frust-c-style-string-literals%> to be enabled"); + add_error (std::move (error)); + return tl::unexpected ( + Parse::Error::Expr::MALFORMED); + } case CHAR_LITERAL: return std::unique_ptr ( new AST::LiteralExpr (tok->get_str (), AST::Literal::CHAR, diff --git a/gcc/rust/parse/rust-parse.h b/gcc/rust/parse/rust-parse.h index 84f7d17435e9..ee27e9674ef4 100644 --- a/gcc/rust/parse/rust-parse.h +++ b/gcc/rust/parse/rust-parse.h @@ -28,6 +28,7 @@ along with GCC; see the file COPYING3. If not see #include "rust-feature-store.h" #include "expected.h" +#include "options.h" namespace Rust { diff --git a/gcc/rust/typecheck/rust-hir-type-check-base.cc b/gcc/rust/typecheck/rust-hir-type-check-base.cc index 6c3cb4054b1c..64a26513cd34 100644 --- a/gcc/rust/typecheck/rust-hir-type-check-base.cc +++ b/gcc/rust/typecheck/rust-hir-type-check-base.cc @@ -386,7 +386,43 @@ TypeCheckBase::resolve_literal (const Analysis::NodeMapping &expr_mappings, TyTy::Region::make_static ()); } break; + case HIR::Literal::LitType::C_STRING: + { + // Throw error if C string literal contains null byte + if (literal.as_string ().find ('\0') != std::string::npos) + { + rust_error_at ( + locus, "null characters in C string literals are not supported"); + infered = new TyTy::ErrorType (expr_mappings.get_hirid (), locus); + break; + } + + auto lang_item_defined + = mappings.lookup_lang_item (LangItem::Kind::CSTR); + + if (!lang_item_defined) + { + rust_error_at (locus, "unable to find lang item: %"); + infered = new TyTy::ErrorType (expr_mappings.get_hirid (), locus); + break; + } + DefId cstr_defid = lang_item_defined.value (); + HIR::Item *item = mappings.lookup_defid (cstr_defid).value (); + + TyTy::BaseType *item_type = nullptr; + bool ok = context->lookup_type (item->get_mappings ().get_hirid (), + &item_type); + + rust_assert (ok); + rust_assert (item_type->get_kind () == TyTy::TypeKind::ADT); + + infered = new TyTy::ReferenceType (expr_mappings.get_hirid (), + TyTy::TyVar (item_type->get_ref ()), + Mutability::Imm, + TyTy::Region::make_static ()); + } + break; default: rust_unreachable (); break; diff --git a/gcc/rust/typecheck/rust-tyty.cc b/gcc/rust/typecheck/rust-tyty.cc index 69ee43c2750c..06a877906e69 100644 --- a/gcc/rust/typecheck/rust-tyty.cc +++ b/gcc/rust/typecheck/rust-tyty.cc @@ -3085,7 +3085,8 @@ ReferenceType::get_region () const bool ReferenceType::is_dyn_object () const { - return is_dyn_slice_type () || is_dyn_str_type () || is_dyn_obj_type (); + return is_dyn_slice_type () || is_dyn_str_type () || is_dyn_obj_type () + || is_dyn_cstr_type (); } bool @@ -3127,6 +3128,27 @@ ReferenceType::is_dyn_obj_type (const TyTy::DynamicObjectType **dyn) const return true; } +bool +ReferenceType::is_dyn_cstr_type (const TyTy::ADTType **adt) const +{ + if (get_base ()->get_kind () != TyTy::TypeKind::ADT) + return false; + + const TyTy::ADTType *adt_ty + = static_cast (get_base ()); + auto &mappings = Analysis::Mappings::get (); + auto cstr_item = mappings.lookup_lang_item (LangItem::Kind::CSTR); + + if (!cstr_item.has_value ()) + return false; + + if (cstr_item.value () != adt_ty->get_id ()) + return false; + + *adt = adt_ty; + return true; +} + void ReferenceType::accept_vis (TyVisitor &vis) { diff --git a/gcc/rust/typecheck/rust-tyty.h b/gcc/rust/typecheck/rust-tyty.h index 516f460e625d..5a7717229ec4 100644 --- a/gcc/rust/typecheck/rust-tyty.h +++ b/gcc/rust/typecheck/rust-tyty.h @@ -1693,6 +1693,7 @@ class ReferenceType : public BaseType bool is_dyn_slice_type (const TyTy::SliceType **slice = nullptr) const; bool is_dyn_str_type (const TyTy::StrType **str = nullptr) const; bool is_dyn_obj_type (const TyTy::DynamicObjectType **dyn = nullptr) const; + bool is_dyn_cstr_type (const TyTy::ADTType **adt = nullptr) const; private: TyVar base; diff --git a/gcc/rust/util/rust-lang-item.cc b/gcc/rust/util/rust-lang-item.cc index 21e2c81e9e69..2f7704546575 100644 --- a/gcc/rust/util/rust-lang-item.cc +++ b/gcc/rust/util/rust-lang-item.cc @@ -92,6 +92,9 @@ const BiMap Rust::LangItem::lang_items = {{ {"slice_u8", Kind::SLICE_U8}, {"slice", Kind::SLICE}, {"str", Kind::STR}, + // NOTE: CStr is not present in Rust 1.49, and is only backported for + // compilation of Rust for Linux with a patched core lib. + {"CStr", Kind::CSTR}, {"f32_runtime", Kind::F32_RUNTIME}, {"f64_runtime", Kind::F64_RUNTIME}, diff --git a/gcc/rust/util/rust-lang-item.h b/gcc/rust/util/rust-lang-item.h index 5a765282b8cd..75f4090b97de 100644 --- a/gcc/rust/util/rust-lang-item.h +++ b/gcc/rust/util/rust-lang-item.h @@ -125,6 +125,9 @@ class LangItem SLICE_U8, SLICE, STR, + // NOTE: CStr is not present in Rust 1.49, and is only backported for + // compilation of Rust for Linux with a patched core lib. + CSTR, F32_RUNTIME, F64_RUNTIME, diff --git a/gcc/testsuite/rust/compile/c_string_null_byte_check.rs b/gcc/testsuite/rust/compile/c_string_null_byte_check.rs new file mode 100644 index 000000000000..040ba9ad468f --- /dev/null +++ b/gcc/testsuite/rust/compile/c_string_null_byte_check.rs @@ -0,0 +1,21 @@ +// { dg-additional-options "-frust-c-style-string-literals" } +#![feature(no_core, lang_items)] +#![no_core] + +type c_char = u8; + +#[lang = "CStr"] +pub struct CStr { + inner: [c_char] +} + +impl CStr { + pub const fn to_ptr(&self) -> *const c_char { + &self.inner as *const [c_char] as *const c_char + } +} + +pub fn main() { + let _fail = c"gc\0crs"; + // { dg-error "null characters in C string literals are not supported" "" { target *-*-* } .-1 } +} \ No newline at end of file diff --git a/gcc/testsuite/rust/execute/torture/c_string.rs b/gcc/testsuite/rust/execute/torture/c_string.rs new file mode 100644 index 000000000000..5aef4db0c21a --- /dev/null +++ b/gcc/testsuite/rust/execute/torture/c_string.rs @@ -0,0 +1,29 @@ +// { dg-additional-options "-frust-c-style-string-literals" } +// { dg-output "gccrs" } +#![feature(no_core, lang_items)] +#![no_core] + +extern "C" { + fn printf(s: *const u8, ...); +} + +type c_char = u8; + +#[lang = "CStr"] +pub struct CStr { + inner: [c_char] +} + +impl CStr { + pub const fn to_ptr(&self) -> *const c_char { + &self.inner as *const [c_char] as *const c_char + } +} + +fn main() -> i32 { + let a = c"gccrs"; + unsafe { + printf(a.to_ptr()); + } + 0 +} diff --git a/gcc/testsuite/rust/execute/torture/c_string_ensure_null_term.rs b/gcc/testsuite/rust/execute/torture/c_string_ensure_null_term.rs new file mode 100644 index 000000000000..26e0fed2173c --- /dev/null +++ b/gcc/testsuite/rust/execute/torture/c_string_ensure_null_term.rs @@ -0,0 +1,50 @@ +// { dg-additional-options "-frust-c-style-string-literals" } +#![feature(no_core, intrinsics, staged_api, lang_items)] +#![no_core] + +#[lang = "sized"] +pub trait Sized {} + +// below's helper code copied from issue-1232.rs +extern "rust-intrinsic" { + #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")] + fn offset(dst: *const T, offset: isize) -> *const T; +} + +#[lang = "const_ptr"] +impl *const T { + pub const unsafe fn offset(self, count: isize) -> *const T { + unsafe { offset(self, count) } + } + + pub const unsafe fn add(self, count: usize) -> Self { + unsafe { self.offset(count as isize) } + } + + pub const fn as_ptr(self) -> *const T { + self as *const T + } +} + +extern "C" { + fn printf(s: *const u8, ...); +} + +type c_char = u8; + +#[lang = "CStr"] +pub struct CStr { + inner: [c_char] +} + +impl CStr { + pub const fn to_ptr(&self) -> *const c_char { + &self.inner as *const [c_char] as *const c_char + } +} + +pub fn main() -> u8 { + let a = c"gccrs"; + let val = unsafe { a.to_ptr().add(5) }; + unsafe { *val } +}