diff --git a/.github/workflows/check-rust-examples.yml b/.github/workflows/check-rust-examples.yml index 7e045123..76b5dd5c 100644 --- a/.github/workflows/check-rust-examples.yml +++ b/.github/workflows/check-rust-examples.yml @@ -424,7 +424,8 @@ jobs: LEGACY_COUNT=0 LEGACY_FILES="" - for file in $(find src/coding-guidelines -name "*.rst" 2>/dev/null); do + # Check both .rst and .rst.inc files (per-guideline structure uses .rst.inc) + for file in $(find src/coding-guidelines \( -name "*.rst" -o -name "*.rst.inc" \) 2>/dev/null); do COUNT=$(grep -c "^\s*\.\. code-block:: rust" "$file" 2>/dev/null || echo 0) if [ "$COUNT" -gt 0 ]; then LEGACY_FILES="$LEGACY_FILES\n- $file: $COUNT occurrences" diff --git a/scripts/extract_rust_examples.py b/scripts/extract_rust_examples.py index a1bcb157..fc2a7958 100644 --- a/scripts/extract_rust_examples.py +++ b/scripts/extract_rust_examples.py @@ -11,6 +11,8 @@ 3. Generates a test crate with all examples as doc tests 4. Runs the tests and reports results +Supports both monolithic chapter files (*.rst) and per-guideline files (*.rst.inc). + Usage: # Extract examples and generate test crate uv run python scripts/extract_rust_examples.py --extract @@ -220,7 +222,7 @@ def extract_rust_examples_from_file(file_path: Path) -> List[RustExample]: - Legacy code-block:: rust directives (for backwards compatibility during migration) Args: - file_path: Path to the RST file + file_path: Path to the RST file (supports .rst and .rst.inc) Returns: List of RustExample objects @@ -326,6 +328,25 @@ def extract_rust_examples_from_file(file_path: Path) -> List[RustExample]: return examples +def find_rst_files(src_dir: Path) -> List[Path]: + """ + Find all RST files in the source directory. + + Searches for both: + - *.rst files (chapter index files, monolithic chapter files) + - *.rst.inc files (per-guideline include files) + + Args: + src_dir: Directory to search + + Returns: + List of Path objects for all RST files found + """ + rst_files = list(src_dir.glob("**/*.rst")) + rst_inc_files = list(src_dir.glob("**/*.rst.inc")) + return rst_files + rst_inc_files + + def extract_all_examples(src_dirs: List[Path], quiet: bool = False) -> List[RustExample]: """ Extract all Rust examples from all RST files in the given directories. @@ -340,17 +361,47 @@ def extract_all_examples(src_dirs: List[Path], quiet: bool = False) -> List[Rust examples = [] for src_dir in src_dirs: - rst_files = list(src_dir.glob("**/*.rst")) + all_files = find_rst_files(src_dir) if not quiet: - print(f"πŸ” Scanning {len(rst_files)} RST files in {src_dir}", file=sys.stderr) - - for file_path in rst_files: - file_examples = extract_rust_examples_from_file(file_path) - if file_examples: - if not quiet: - print(f" πŸ“„ {file_path.name}: {len(file_examples)} examples", file=sys.stderr) - examples.extend(file_examples) + print(f"πŸ” Scanning {len(all_files)} RST files in {src_dir}", file=sys.stderr) + + # Group files by parent directory (chapter) + files_by_chapter: Dict[str, List[Path]] = {} + for file_path in all_files: + # Get chapter name (parent directory relative to src_dir) + try: + rel_path = file_path.relative_to(src_dir) + if len(rel_path.parts) > 1: + chapter = rel_path.parts[0] + else: + chapter = "(root)" + except ValueError: + chapter = "(other)" + + if chapter not in files_by_chapter: + files_by_chapter[chapter] = [] + files_by_chapter[chapter].append(file_path) + + # Process files grouped by chapter + for chapter in sorted(files_by_chapter.keys()): + chapter_files = files_by_chapter[chapter] + chapter_has_examples = False + file_results: List[Tuple[Path, List[RustExample]]] = [] + + # Extract examples from each file + for file_path in sorted(chapter_files): + file_examples = extract_rust_examples_from_file(file_path) + if file_examples: + file_results.append((file_path, file_examples)) + examples.extend(file_examples) + chapter_has_examples = True + + # Print chapter heading and files with examples + if chapter_has_examples and not quiet: + print(f"\n {chapter}/", file=sys.stderr) + for file_path, file_examples in file_results: + print(f" {file_path.name}: {len(file_examples)} examples", file=sys.stderr) if not quiet: print(f"\nπŸ“Š Total: {len(examples)} examples found", file=sys.stderr) @@ -683,14 +734,15 @@ def main(): filter_min_version=args.filter_min_version, filter_default=args.filter_default, ) - if args.verbose: - print(f"πŸ” Filtered {original_count} -> {len(examples)} examples") + filtered_count = original_count - len(examples) + if filtered_count > 0: + print(f"\nπŸ” Filtered: {len(examples)} examples to test ({filtered_count} excluded)") if args.filter_channel: print(f" Filter: channel={args.filter_channel}") if args.filter_min_version: - print(f" Filter: min_version={args.filter_min_version}") + print(f" Filter: min_version>={args.filter_min_version}") if args.filter_default: - print(" Filter: default (no special requirements)") + print(f" Filter: default toolchain only (excluded {filtered_count} requiring nightly/beta or specific version)") if args.extract or args.test: # Generate test crate diff --git a/scripts/generate-rst-comment.py b/scripts/generate-rst-comment.py index 89d8af68..1dcc0dca 100644 --- a/scripts/generate-rst-comment.py +++ b/scripts/generate-rst-comment.py @@ -13,6 +13,7 @@ import json import os +import re import sys # Add the scripts directory to Python path so we can import guideline_utils @@ -28,6 +29,20 @@ ) +def extract_guideline_id(rst_content: str) -> str: + """ + Extract the guideline ID from RST content. + + Args: + rst_content: The generated RST content + + Returns: + The guideline ID (e.g., "gui_abc123XYZ") or empty string if not found + """ + match = re.search(r':id:\s*(gui_[a-zA-Z0-9]+)', rst_content) + return match.group(1) if match else "" + + def generate_comment(rst_content: str, chapter: str) -> str: """ Generate a formatted GitHub comment with instructions and RST content. @@ -40,17 +55,39 @@ def generate_comment(rst_content: str, chapter: str) -> str: Formatted Markdown comment string """ chapter_slug = chapter_to_filename(chapter) - target_file = f"src/coding-guidelines/{chapter_slug}.rst" + guideline_id = extract_guideline_id(rst_content) + + # Determine target path based on whether we have a guideline ID + if guideline_id: + target_dir = f"src/coding-guidelines/{chapter_slug}/" + target_file = f"{target_dir}{guideline_id}.rst.inc" + file_instructions = f""" +### πŸ“ Target Location + +Create a new file: `{target_file}` + +> **Note:** The `.rst.inc` extension prevents Sphinx from auto-discovering the file. +> It will be included via the chapter's `index.rst`.""" + else: + # Fallback for legacy structure (shouldn't happen with new template) + target_file = f"src/coding-guidelines/{chapter_slug}.rst" + file_instructions = f""" +### πŸ“ Target File + +Add this guideline to: `{target_file}`""" comment = f"""## πŸ“‹ RST Preview for Coding Guideline This is an automatically generated preview of your coding guideline in reStructuredText format. +{file_instructions} -### πŸ“ Target File +### πŸ“ How to Use This -Add this guideline to: `{target_file}` +**Option A: Automatic (Recommended)** -### πŸ“ How to Use This +Once this issue is approved, a maintainer will add the `sign-off: create pr from issue` label, which automatically creates a PR with the guideline file. + +**Option B: Manual** 1. **Fork the repository** (if you haven't already) and clone it locally 2. **Create a new branch** from `main`: @@ -59,19 +96,27 @@ def generate_comment(rst_content: str, chapter: str) -> str: git pull origin main git checkout -b guideline/your-descriptive-branch-name ``` -3. **Open the target file** `{target_file}` in your editor -4. **Copy the RST content** below and paste it at the end of the file (before any final directives if present) -5. **Build locally** to verify the guideline renders correctly: +3. **Create the guideline file**: + ```bash + mkdir -p src/coding-guidelines/{chapter_slug} + ``` +4. **Copy the RST content** below into a new file named `{guideline_id}.rst.inc` +5. **Update the chapter index** - Add an include directive to `src/coding-guidelines/{chapter_slug}/index.rst`: + ```rst + .. include:: {guideline_id}.rst.inc + ``` + Keep the includes in alphabetical order by guideline ID. +6. **Build locally** to verify the guideline renders correctly: ```bash ./make.py ``` -6. **Commit and push** your changes: +7. **Commit and push** your changes: ```bash - git add {target_file} + git add src/coding-guidelines/{chapter_slug}/ git commit -m "Add guideline: " git push origin guideline/your-descriptive-branch-name ``` -7. **Open a Pull Request** against `main` +8. **Open a Pull Request** against `main`
πŸ“„ Click to expand RST content diff --git a/scripts/guideline_utils.py b/scripts/guideline_utils.py index 0c4d4bdc..6f35c6f0 100644 --- a/scripts/guideline_utils.py +++ b/scripts/guideline_utils.py @@ -12,6 +12,7 @@ import os import re import sys +from pathlib import Path from textwrap import dedent, indent import pypandoc @@ -25,6 +26,24 @@ issue_header_map, ) +# ============================================================================= +# Constants for per-guideline file structure +# ============================================================================= + +# Header comment for individual guideline files +GUIDELINE_FILE_HEADER = """\ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +""" + +# Default guidelines directory +DEFAULT_GUIDELINES_DIR = Path("src/coding-guidelines") + + +# ============================================================================= +# Markdown to RST conversion utilities +# ============================================================================= def md_to_rst(markdown: str) -> str: """Convert Markdown text to reStructuredText using Pandoc.""" @@ -91,6 +110,10 @@ def normalize_md(issue_body: str) -> str: return issue_body +# ============================================================================= +# Issue parsing utilities +# ============================================================================= + def extract_form_fields(issue_body: str) -> dict: """ Parse issue body (from GitHub issue template) into a dict of field values. @@ -133,6 +156,10 @@ def extract_form_fields(issue_body: str) -> dict: return fields +# ============================================================================= +# RST generation utilities +# ============================================================================= + def format_code_block(code: str, lang: str = "rust") -> str: """ Format a code block for RST output, stripping markdown fences if present. @@ -212,6 +239,68 @@ def get(key): return guideline_text +# ============================================================================= +# ID extraction utilities +# ============================================================================= + +def extract_guideline_id(content: str) -> str: + """ + Extract the guideline ID from RST content. + + Args: + content: RST content containing a guideline directive + + Returns: + The guideline ID (e.g., "gui_abc123XYZ") or empty string if not found + """ + match = re.search(r':id:\s*(gui_[a-zA-Z0-9]+)', content) + return match.group(1) if match else "" + + +def extract_all_ids(content: str) -> dict: + """ + Extract all IDs from RST content. + + Args: + content: RST content + + Returns: + Dictionary with keys 'guideline', 'rationale', 'compliant', 'non_compliant' + """ + ids = { + 'guideline': '', + 'rationale': '', + 'compliant': '', + 'non_compliant': '' + } + + # Guideline ID + match = re.search(r':id:\s*(gui_[a-zA-Z0-9]+)', content) + if match: + ids['guideline'] = match.group(1) + + # Rationale ID + match = re.search(r':id:\s*(rat_[a-zA-Z0-9]+)', content) + if match: + ids['rationale'] = match.group(1) + + # Compliant example ID + match = re.search(r':id:\s*(compl_ex_[a-zA-Z0-9]+)', content) + if match: + ids['compliant'] = match.group(1) + + # Non-compliant example ID + match = re.search(r':id:\s*(non_compl_ex_[a-zA-Z0-9]+)', content) + if match: + ids['non_compliant'] = match.group(1) + + return ids + + +# ============================================================================= +# Chapter/directory name utilities +# ============================================================================= + def chapter_to_filename(chapter: str) -> str: """ Convert chapter name to filename slug. @@ -225,15 +314,232 @@ def chapter_to_filename(chapter: str) -> str: return chapter.lower().replace(" ", "-") -def save_guideline_file(content: str, chapter: str): +def chapter_to_dirname(chapter: str) -> str: + """ + Convert chapter name to directory name (same as filename slug). + + Args: + chapter: Chapter name (e.g., "Associated Items", "Concurrency") + + Returns: + Directory name (e.g., "associated-items", "concurrency") + """ + return chapter_to_filename(chapter) + + +def dirname_to_chapter(dirname: str) -> str: + """ + Convert directory name back to chapter name. + + Args: + dirname: Directory name (e.g., "associated-items") + + Returns: + Chapter name (e.g., "Associated Items") + """ + return dirname.replace("-", " ").title() + + +# ============================================================================= +# Index management for per-guideline structure +# ============================================================================= + +def add_include_to_chapter_index( + chapter_dir: Path, + guideline_filename: str, +) -> bool: """ - Append a guideline to a chapter file. + Add an include directive to a chapter's index.rst, maintaining alphabetical order. Args: - content: The RST content to append - chapter: The chapter name + chapter_dir: Path to the chapter directory + guideline_filename: Filename of the guideline (e.g., "gui_abc123.rst.inc") + + Returns: + True if successful, False otherwise """ - filename = f"src/coding-guidelines/{chapter_to_filename(chapter)}.rst" - with open(filename, "a", encoding="utf-8") as f: + index_path = chapter_dir / "index.rst" + + if not index_path.exists(): + print(f"Warning: Index file not found: {index_path}") + return False + + content = index_path.read_text() + + # Check if already included + if guideline_filename in content: + print(f"Note: {guideline_filename} already in index") + return True + + # Find existing include directives and their position + include_pattern = re.compile(r'^(\s*)\.\.\ include::\s+(gui_[a-zA-Z0-9]+\.rst\.inc)\s*$', re.MULTILINE) + matches = list(include_pattern.finditer(content)) + + new_include = f".. include:: {guideline_filename}" + + if matches: + # Get the indentation from existing includes + indent_str = matches[0].group(1) + new_include = f"{indent_str}.. include:: {guideline_filename}" + + # Find where to insert alphabetically + existing_files = [(m.group(2), m.start(), m.end()) for m in matches] + + insert_pos = None + for filename, start, end in existing_files: + if guideline_filename < filename: + insert_pos = start + break + + if insert_pos is None: + # Add at end (after last include) + last_end = existing_files[-1][2] + content = content[:last_end] + "\n" + new_include + content[last_end:] + else: + # Insert before the found position + content = content[:insert_pos] + new_include + "\n" + content[insert_pos:] + else: + # No existing includes - add at end of file + content = content.rstrip() + "\n\n" + new_include + "\n" + + index_path.write_text(content) + return True + + +def remove_include_from_chapter_index( + chapter_dir: Path, + guideline_filename: str, +) -> bool: + """ + Remove an include directive from a chapter's index.rst. + + Args: + chapter_dir: Path to the chapter directory + guideline_filename: Filename of the guideline to remove + + Returns: + True if successful, False otherwise + """ + index_path = chapter_dir / "index.rst" + + if not index_path.exists(): + return False + + content = index_path.read_text() + + # Remove the include line + pattern = re.compile(rf'^\s*\.\.\ include::\s+{re.escape(guideline_filename)}\s*\n?', re.MULTILINE) + new_content = pattern.sub('', content) + + if new_content != content: + index_path.write_text(new_content) + return True + + return False + + +# ============================================================================= +# File operations +# ============================================================================= + +def save_guideline_file( + content: str, + chapter: str, + guidelines_dir: Path = None, +) -> Path: + """ + Save a guideline to a per-guideline file in the chapter directory. + + This creates: + 1. The chapter directory if it doesn't exist + 2. A new file named {guideline_id}.rst.inc + 3. Updates the chapter's index.rst with an include directive + + Args: + content: The RST content for the guideline + chapter: The chapter name (e.g., "Expressions") + guidelines_dir: Base guidelines directory (default: src/coding-guidelines) + + Returns: + Path to the created file + """ + if guidelines_dir is None: + guidelines_dir = DEFAULT_GUIDELINES_DIR + + chapter_slug = chapter_to_dirname(chapter) + chapter_dir = guidelines_dir / chapter_slug + + # Check if per-guideline structure exists (chapter is a directory) + if not chapter_dir.is_dir(): + # Fall back to legacy monolithic file structure + print(f"Note: Chapter directory {chapter_dir} not found.") + print(" Using legacy file structure. Run split_guidelines.py to migrate.") + return save_guideline_file_legacy(content, chapter, guidelines_dir) + + # Extract guideline ID + guideline_id = extract_guideline_id(content) + if not guideline_id: + raise ValueError("Could not extract guideline ID from content") + + # Create the guideline file + guideline_filename = f"{guideline_id}.rst.inc" + guideline_path = chapter_dir / guideline_filename + + # Add header and write content + full_content = GUIDELINE_FILE_HEADER + content.strip() + "\n" + guideline_path.write_text(full_content) + print(f"Created guideline file: {guideline_path}") + + # Update the chapter index + if add_include_to_chapter_index(chapter_dir, guideline_filename): + print(f"Updated index: {chapter_dir / 'index.rst'}") + + return guideline_path + + +def save_guideline_file_legacy( + content: str, + chapter: str, + guidelines_dir: Path = None, +) -> Path: + """ + Append a guideline to a monolithic chapter file (legacy structure). + + Args: + content: The RST content for the guideline + chapter: The chapter name (e.g., "Expressions") + guidelines_dir: Base guidelines directory (default: src/coding-guidelines) + + Returns: + Path to the chapter file + """ + if guidelines_dir is None: + guidelines_dir = DEFAULT_GUIDELINES_DIR + + chapter_slug = chapter_to_filename(chapter) + chapter_file = guidelines_dir / f"{chapter_slug}.rst" + + with open(chapter_file, "a", encoding="utf-8") as f: f.write(content) - print(f"Saved guideline to {filename}") + + print(f"Appended guideline to: {chapter_file}") + return chapter_file + + +def list_guidelines_in_chapter(chapter_dir: Path) -> list: + """ + List all guideline files in a chapter directory. + + Args: + chapter_dir: Path to the chapter directory + + Returns: + List of guideline IDs found + """ + guidelines = [] + + for file_path in chapter_dir.glob("gui_*.rst.inc"): + guideline_id = file_path.stem # Remove .rst.inc extension + guidelines.append(guideline_id) + + return sorted(guidelines) diff --git a/scripts/migrate_rust_examples.py b/scripts/migrate_rust_examples.py index d6bd7bba..998b5000 100644 --- a/scripts/migrate_rust_examples.py +++ b/scripts/migrate_rust_examples.py @@ -11,6 +11,8 @@ 2. Converts them to rust-example:: directives 3. Optionally runs a compilation check to suggest appropriate attributes +Supports both monolithic chapter files (*.rst) and per-guideline files (*.rst.inc). + Usage: # Preview changes (dry run) uv run python scripts/migrate_rust_examples.py --dry-run @@ -56,8 +58,22 @@ def find_rst_files(src_dir: Path) -> List[Path]: - """Find all RST files in the source directory.""" - return list(src_dir.glob("**/*.rst")) + """ + Find all RST files in the source directory. + + Searches for both: + - *.rst files (chapter index files, monolithic chapter files) + - *.rst.inc files (per-guideline include files) + + Args: + src_dir: Directory to search + + Returns: + List of Path objects for all RST files found + """ + rst_files = list(src_dir.glob("**/*.rst")) + rst_inc_files = list(src_dir.glob("**/*.rst.inc")) + return rst_files + rst_inc_files def extract_code_block_content(content: str, start_pos: int, base_indent: str) -> Tuple[str, int]: @@ -303,7 +319,7 @@ def process_file( Process a single RST file. Args: - file_path: Path to the RST file + file_path: Path to the RST file (supports .rst and .rst.inc) dry_run: If True, don't write changes detect_failures: Whether to detect compilation failures prelude: Optional prelude code @@ -383,9 +399,9 @@ def main(): else: print(f"⚠️ Prelude file not found: {prelude_path}") - # Find and process RST files - rst_files = find_rst_files(src_dir) - print(f"πŸ” Found {len(rst_files)} RST files in {src_dir}") + # Find and process RST files (both .rst and .rst.inc) + all_files = find_rst_files(src_dir) + print(f"πŸ” Found {len(all_files)} RST files in {src_dir}") if args.dry_run: print("πŸ“‹ DRY RUN - no files will be modified") @@ -393,7 +409,7 @@ def main(): total_changes = 0 files_changed = 0 - for file_path in rst_files: + for file_path in all_files: changes = process_file( file_path, dry_run=args.dry_run, diff --git a/scripts/split_guidelines.py b/scripts/split_guidelines.py new file mode 100644 index 00000000..dc54ebf3 --- /dev/null +++ b/scripts/split_guidelines.py @@ -0,0 +1,490 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT OR Apache-2.0 +# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +""" +Migration script to split monolithic chapter RST files into per-guideline files. + +This script: +1. Parses existing chapter files (e.g., expressions.rst) +2. Extracts each guideline with its nested content (rationale, examples) +3. Creates a subdirectory per chapter (e.g., expressions/) +4. Writes each guideline to its own file (e.g., expressions/gui_xxx.rst) +5. Generates a chapter index.rst that includes all guidelines + +Design decisions for future Option 2 migration: +- Each guideline file is self-contained (has its own default-domain, SPDX header) +- Files are named by guideline ID for stable URLs +- Alphabetical ordering enables predictable merge conflict resolution +- Chapter index only contains includes, no guideline content + +Usage: + # Dry run - see what would happen + uv run python scripts/split_guidelines.py --dry-run + + # Process a single chapter + uv run python scripts/split_guidelines.py --chapter expressions + + # Process all chapters + uv run python scripts/split_guidelines.py --all + + # Specify custom source directory + uv run python scripts/split_guidelines.py --all --src-dir src/coding-guidelines +""" + +import argparse +import re +import sys +from pathlib import Path +from typing import List, Tuple + +# SPDX header to prepend to each generated file +SPDX_HEADER = """\ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +""" + +# Pattern to match guideline directive start +GUIDELINE_PATTERN = re.compile( + r'^(\.\. guideline::)\s*(.*?)$', + re.MULTILINE +) + +# Pattern to extract guideline ID from :id: option +ID_PATTERN = re.compile(r':id:\s*(gui_[A-Za-z0-9_]+)') + + +def find_guideline_boundaries(content: str) -> List[Tuple[int, int, str]]: + """ + Find the start and end positions of each guideline in the content. + + Returns: + List of (start_pos, end_pos, guideline_id) tuples + """ + boundaries = [] + matches = list(GUIDELINE_PATTERN.finditer(content)) + + for i, match in enumerate(matches): + start = match.start() + + # End is either the start of the next guideline or end of file + if i + 1 < len(matches): + end = matches[i + 1].start() + else: + end = len(content) + + # Extract the guideline content to find the ID + guideline_content = content[start:end] + id_match = ID_PATTERN.search(guideline_content) + + if id_match: + guideline_id = id_match.group(1) + else: + # Fallback: generate ID from position + guideline_id = f"gui_unknown_{i}" + print(f" Warning: Could not find ID for guideline at position {start}", file=sys.stderr) + + boundaries.append((start, end, guideline_id)) + + return boundaries + + +def extract_chapter_header(content: str, first_guideline_start: int) -> str: + """ + Extract any content before the first guideline (chapter title, intro text). + + Returns: + The header content, stripped of SPDX and default-domain (we'll add fresh ones) + """ + header = content[:first_guideline_start] + + # Remove existing SPDX header + header = re.sub(r'\.\. SPDX-License-Identifier:.*?\n', '', header) + header = re.sub(r'\s*SPDX-FileCopyrightText:.*?\n', '', header) + + # Remove existing default-domain + header = re.sub(r'\.\. default-domain::.*?\n\n?', '', header) + + return header.strip() + + +def extract_guideline_content(content: str, start: int, end: int) -> str: + """ + Extract a single guideline's content. + + Returns: + The guideline content, ready to be written to its own file + """ + guideline = content[start:end].rstrip() + return guideline + + +def generate_chapter_index(chapter_name: str, chapter_title: str, guideline_ids: List[str], header_content: str = "") -> str: + """ + Generate the chapter index.rst content with includes. + + Args: + chapter_name: Directory name (e.g., "expressions") + chapter_title: Display title (e.g., "Expressions") + guideline_ids: List of guideline IDs, will be sorted alphabetically + header_content: Optional introductory content after the title + """ + # Sort IDs alphabetically for predictable ordering + sorted_ids = sorted(guideline_ids) + + lines = [ + ".. SPDX-License-Identifier: MIT OR Apache-2.0", + " SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors", + "", + ".. default-domain:: coding-guidelines", + "", + chapter_title, + "=" * len(chapter_title), + "", + ] + + if header_content: + lines.append(header_content) + lines.append("") + + # Add includes for each guideline (using .rst.inc extension) + for gid in sorted_ids: + lines.append(f".. include:: {gid}.rst.inc") + + lines.append("") # Trailing newline + + return "\n".join(lines) + + +def parse_chapter_file(filepath: Path) -> Tuple[str, str, List[Tuple[str, str]]]: + """ + Parse a chapter file and extract its components. + + Returns: + Tuple of (chapter_title, header_content, [(guideline_id, guideline_content), ...]) + For empty chapters, returns (title, header, []) with an empty guidelines list. + """ + content = filepath.read_text() + + # Find all guidelines + boundaries = find_guideline_boundaries(content) + + if not boundaries: + # Empty chapter - extract title from content + title_match = re.search(r'^([^\n]+)\n=+', content) + if title_match: + chapter_title = title_match.group(1).strip() + else: + chapter_title = filepath.stem.replace("-", " ").title() + return chapter_title, "", [] + + # Extract header (everything before first guideline) + header = extract_chapter_header(content, boundaries[0][0]) + + # Extract chapter title from header + title_match = re.search(r'^([^\n]+)\n=+', header) + if title_match: + chapter_title = title_match.group(1).strip() + # Remove title from header content + header = header[title_match.end():].strip() + else: + chapter_title = filepath.stem.replace("-", " ").title() + + # Extract each guideline + guidelines = [] + for start, end, gid in boundaries: + guideline_content = extract_guideline_content(content, start, end) + guidelines.append((gid, guideline_content)) + + return chapter_title, header, guidelines + + +def split_chapter( + src_file: Path, + output_dir: Path, + dry_run: bool = False, + verbose: bool = False +) -> Tuple[int, List[str]]: + """ + Split a chapter file into per-guideline files. + + Args: + src_file: Path to the source chapter file (e.g., expressions.rst) + output_dir: Base output directory (e.g., src/coding-guidelines) + dry_run: If True, don't write files + verbose: If True, print detailed progress + + Returns: + Tuple of (number of guidelines, list of guideline IDs) + """ + chapter_name = src_file.stem + chapter_dir = output_dir / chapter_name + + print(f"\nProcessing {src_file.name}...") + + # Parse the chapter + chapter_title, header_content, guidelines = parse_chapter_file(src_file) + + if not guidelines: + print(" No guidelines found - creating empty chapter structure") + else: + print(f" Found {len(guidelines)} guidelines") + + print(f" Chapter title: {chapter_title}") + + if verbose: + for gid, _ in guidelines: + print(f" - {gid}") + + if dry_run: + print(f" Would create directory: {chapter_dir}") + print(f" Would create {len(guidelines)} guideline files (.rst.inc)") + print(" Would create index.rst") + return len(guidelines), [g[0] for g in guidelines] + + # Create chapter directory + chapter_dir.mkdir(parents=True, exist_ok=True) + + # Write each guideline file (using .rst.inc extension to prevent Sphinx auto-discovery) + guideline_ids = [] + for gid, content in guidelines: + guideline_file = chapter_dir / f"{gid}.rst.inc" + full_content = SPDX_HEADER + content + "\n" + guideline_file.write_text(full_content) + guideline_ids.append(gid) + + if verbose: + print(f" Created {guideline_file.name}") + + # Generate and write index + index_content = generate_chapter_index( + chapter_name, + chapter_title, + guideline_ids, + header_content + ) + index_file = chapter_dir / "index.rst" + index_file.write_text(index_content) + print(f" Created {index_file}") + + return len(guidelines), guideline_ids + + +def update_main_index( + index_file: Path, + chapter_names: List[str], + dry_run: bool = False +): + """ + Update the main coding-guidelines/index.rst to point to chapter subdirectories. + + This changes entries like 'expressions' to 'expressions/index'. + """ + if not index_file.exists(): + print(f"Warning: Main index not found at {index_file}", file=sys.stderr) + return + + content = index_file.read_text() + original = content + + updated_chapters = [] + for chapter in chapter_names: + # Replace 'chapter' with 'chapter/index' in toctree + # Match the chapter name at the end of a line (with optional trailing whitespace) + # but only if it's not already followed by /index + pattern = rf'(^[ \t]+){re.escape(chapter)}([ \t]*$)' + + # Check if this chapter is in the content and not already updated + if re.search(pattern, content, re.MULTILINE): + replacement = rf'\g<1>{chapter}/index\g<2>' + content = re.sub(pattern, replacement, content, flags=re.MULTILINE) + updated_chapters.append(chapter) + + if content != original: + if dry_run: + print(f"\nWould update main index at {index_file}") + print(f" Chapters to update: {', '.join(updated_chapters)}") + else: + index_file.write_text(content) + print(f"\nUpdated main index at {index_file}") + print(f" Updated chapters: {', '.join(updated_chapters)}") + else: + print("\nMain index already up to date (or no matching chapters found)") + + +def get_chapter_files(src_dir: Path) -> List[Path]: + """ + Get list of chapter RST files (excluding index.rst and non-guideline files). + """ + # These are known chapter files based on index.rst toctree + known_chapters = [ + "types-and-traits", + "patterns", + "expressions", + "values", + "statements", + "functions", + "associated-items", + "implementations", + "generics", + "attributes", + "entities-and-resolution", + "ownership-and-destruction", + "exceptions-and-errors", + "concurrency", + "program-structure-and-compilation", + "unsafety", + "macros", + "ffi", + "inline-assembly", + ] + + chapter_files = [] + for name in known_chapters: + filepath = src_dir / f"{name}.rst" + if filepath.exists(): + chapter_files.append(filepath) + + return chapter_files + + +def main(): + parser = argparse.ArgumentParser( + description="Split chapter RST files into per-guideline files" + ) + + parser.add_argument( + "--chapter", + type=str, + help="Process a single chapter (e.g., 'expressions')" + ) + parser.add_argument( + "--all", + action="store_true", + help="Process all chapter files" + ) + parser.add_argument( + "--src-dir", + type=str, + default="src/coding-guidelines", + help="Source directory containing chapter files" + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would happen without making changes" + ) + parser.add_argument( + "-v", "--verbose", + action="store_true", + help="Verbose output" + ) + parser.add_argument( + "--update-index", + action="store_true", + help="Update main index.rst to point to chapter subdirectories" + ) + parser.add_argument( + "--cleanup", + action="store_true", + help="Remove old chapter files after successful migration (use with caution!)" + ) + + args = parser.parse_args() + + if not args.chapter and not args.all: + parser.print_help() + print("\nError: Specify --chapter or --all", file=sys.stderr) + sys.exit(1) + + src_dir = Path(args.src_dir) + if not src_dir.exists(): + print(f"Error: Source directory not found: {src_dir}", file=sys.stderr) + sys.exit(1) + + if args.dry_run: + print("=== DRY RUN - No files will be modified ===\n") + + # Determine which files to process + if args.chapter: + chapter_file = src_dir / f"{args.chapter}.rst" + if not chapter_file.exists(): + print(f"Error: Chapter file not found: {chapter_file}", file=sys.stderr) + sys.exit(1) + files_to_process = [chapter_file] + else: + files_to_process = get_chapter_files(src_dir) + + print(f"Found {len(files_to_process)} chapter file(s) to process") + + # Process each file + total_guidelines = 0 + processed_chapters = [] + empty_chapters = [] + + for filepath in files_to_process: + try: + count, _ = split_chapter( + filepath, + src_dir, + dry_run=args.dry_run, + verbose=args.verbose + ) + total_guidelines += count + processed_chapters.append(filepath.stem) + if count == 0: + empty_chapters.append(filepath.stem) + except Exception as e: + print(f"Error processing {filepath}: {e}", file=sys.stderr) + if args.verbose: + import traceback + traceback.print_exc() + + print(f"\n{'Would process' if args.dry_run else 'Processed'} {total_guidelines} guidelines across {len(processed_chapters)} chapters") + if empty_chapters: + print(f" ({len(empty_chapters)} chapters have no guidelines yet: {', '.join(empty_chapters)})") + + # Optionally update main index + if args.update_index and processed_chapters: + main_index = src_dir / "index.rst" + update_main_index(main_index, processed_chapters, dry_run=args.dry_run) + + # Optionally cleanup old files + if args.cleanup and processed_chapters and not args.dry_run: + print("\n=== Cleaning up old chapter files ===") + for chapter in processed_chapters: + old_file = src_dir / f"{chapter}.rst" + if old_file.exists(): + old_file.unlink() + print(f" Removed {old_file}") + elif args.cleanup and args.dry_run: + print("\n=== Would remove these old chapter files ===") + for chapter in processed_chapters: + old_file = src_dir / f"{chapter}.rst" + if old_file.exists(): + print(f" Would remove {old_file}") + + # Print next steps + if not args.dry_run: + print("\n=== Next Steps ===") + if not args.update_index: + print("1. Run again with --update-index to update the main toctree") + if not args.cleanup: + print("2. Remove old chapter files manually, or run again with --cleanup:") + for chapter in processed_chapters: + old_file = src_dir / f"{chapter}.rst" + if old_file.exists(): + print(f" rm {old_file}") + print("3. Build the documentation to verify: ./make.py") + print("4. Update any tooling (auto-pr-helper.py, etc.)") + else: + print("\n=== To apply changes ===") + print("Run without --dry-run:") + print(" uv run python scripts/split_guidelines.py --all --update-index --cleanup") + + +if __name__ == "__main__": + main() diff --git a/src/coding-guidelines/associated-items.rst b/src/coding-guidelines/associated-items/gui_ot2Zt3dd6of1.rst.inc similarity index 99% rename from src/coding-guidelines/associated-items.rst rename to src/coding-guidelines/associated-items/gui_ot2Zt3dd6of1.rst.inc index dcf3e805..3f59996d 100644 --- a/src/coding-guidelines/associated-items.rst +++ b/src/coding-guidelines/associated-items/gui_ot2Zt3dd6of1.rst.inc @@ -3,9 +3,6 @@ .. default-domain:: coding-guidelines -Associated Items -================ - .. guideline:: Recursive function are not allowed :id: gui_ot2Zt3dd6of1 :category: required diff --git a/src/coding-guidelines/associated-items/index.rst b/src/coding-guidelines/associated-items/index.rst new file mode 100644 index 00000000..25957add --- /dev/null +++ b/src/coding-guidelines/associated-items/index.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +Associated Items +================ + +.. include:: gui_ot2Zt3dd6of1.rst.inc diff --git a/src/coding-guidelines/attributes.rst b/src/coding-guidelines/attributes/index.rst similarity index 99% rename from src/coding-guidelines/attributes.rst rename to src/coding-guidelines/attributes/index.rst index f952c3d4..ed679e77 100644 --- a/src/coding-guidelines/attributes.rst +++ b/src/coding-guidelines/attributes/index.rst @@ -5,3 +5,4 @@ Attributes ========== + diff --git a/src/coding-guidelines/concurrency.rst b/src/coding-guidelines/concurrency/index.rst similarity index 99% rename from src/coding-guidelines/concurrency.rst rename to src/coding-guidelines/concurrency/index.rst index 701b2b79..9d7f2e53 100644 --- a/src/coding-guidelines/concurrency.rst +++ b/src/coding-guidelines/concurrency/index.rst @@ -5,3 +5,4 @@ Concurrency =========== + diff --git a/src/coding-guidelines/entities-and-resolution.rst b/src/coding-guidelines/entities-and-resolution/index.rst similarity index 88% rename from src/coding-guidelines/entities-and-resolution.rst rename to src/coding-guidelines/entities-and-resolution/index.rst index c261e021..2bb81665 100644 --- a/src/coding-guidelines/entities-and-resolution.rst +++ b/src/coding-guidelines/entities-and-resolution/index.rst @@ -3,5 +3,6 @@ .. default-domain:: coding-guidelines -Entities and Resolution +Entities And Resolution ======================= + diff --git a/src/coding-guidelines/exceptions-and-errors.rst b/src/coding-guidelines/exceptions-and-errors/index.rst similarity index 88% rename from src/coding-guidelines/exceptions-and-errors.rst rename to src/coding-guidelines/exceptions-and-errors/index.rst index be0fd7cb..4f618069 100644 --- a/src/coding-guidelines/exceptions-and-errors.rst +++ b/src/coding-guidelines/exceptions-and-errors/index.rst @@ -3,5 +3,6 @@ .. default-domain:: coding-guidelines -Exceptions and Errors +Exceptions And Errors ===================== + diff --git a/src/coding-guidelines/expressions.rst b/src/coding-guidelines/expressions.rst deleted file mode 100644 index 4e85019e..00000000 --- a/src/coding-guidelines/expressions.rst +++ /dev/null @@ -1,1194 +0,0 @@ -.. SPDX-License-Identifier: MIT OR Apache-2.0 - SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors - -.. default-domain:: coding-guidelines - -Expressions -=========== - - -.. guideline:: Ensure that integer operations do not result in arithmetic overflow - :id: gui_dCquvqE1csI3 - :category: required - :status: draft - :release: 1.0 - latest - :fls: fls_oFIRXBPXu6Zv - :decidability: decidable - :scope: system - :tags: security, performance, numerics - - Eliminate `arithmetic overflow `__ of both signed and unsigned integer types. - Any wraparound behavior must be explicitly specified to ensure the same behavior in both debug and release modes. - - This rule applies to the following primitive types: - - * ``i8`` - * ``i16`` - * ``i32`` - * ``i64`` - * ``i128`` - * ``u8`` - * ``u16`` - * ``u32`` - * ``u64`` - * ``u128`` - * ``usize`` - * ``isize`` - - .. rationale:: - :id: rat_LvrS1jTCXEOk - :status: draft - - Eliminate arithmetic overflow to avoid runtime panics and unexpected wraparound behavior. - Arithmetic overflow will panic in debug mode, but wraparound in release mode, resulting in inconsistent behavior. - Use explicit `wrapping `_ or - `saturating `_ semantics where these behaviors are intentional. - Range checking can be used to eliminate the possibility of arithmetic overflow. - - .. non_compliant_example:: - :id: non_compl_ex_cCh2RQUXeH0N - :status: draft - - This noncompliant code example can result in arithmetic overflow during the addition of the signed operands ``si_a`` and ``si_b``: - - .. rust-example:: - - fn add(si_a: i32, si_b: i32) { - let _sum: i32 = si_a + si_b; - // ... - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_BgUHiRB4kc4b_1 - :status: draft - - This compliant solution ensures that the addition operation cannot result in arithmetic overflow, - based on the maximum range of a signed 32-bit integer. - Functions such as - `overflowing_add `_, - `overflowing_sub `_, and - `overflowing_mul `_ - can also be used to detect overflow. - Code that invoked these functions would typically further restrict the range of possible values, - based on the anticipated range of the inputs. - - .. rust-example:: - - # #[derive(Debug)] - # enum ArithmeticError { Overflow, DivisionByZero } - use std::i32::{MAX as INT_MAX, MIN as INT_MIN}; - - fn add(si_a: i32, si_b: i32) -> Result { - if (si_b > 0 && si_a > INT_MAX - si_b) - || (si_b < 0 && si_a < INT_MIN - si_b) - { - Err(ArithmeticError::Overflow) - } else { - Ok(si_a + si_b) - } - } - - fn sub(si_a: i32, si_b: i32) -> Result { - if (si_b < 0 && si_a > INT_MAX + si_b) - || (si_b > 0 && si_a < INT_MIN + si_b) - { - Err(ArithmeticError::Overflow) - } else { - Ok(si_a - si_b) - } - } - - fn mul(si_a: i32, si_b: i32) -> Result { - if si_a == 0 || si_b == 0 { - return Ok(0); - } - - // Detect overflow before performing multiplication - if (si_a == -1 && si_b == INT_MIN) || (si_b == -1 && si_a == INT_MIN) { - Err(ArithmeticError::Overflow) - } else if (si_a > 0 && (si_b > INT_MAX / si_a || si_b < INT_MIN / si_a)) - || (si_a < 0 && (si_b > INT_MIN / si_a || si_b < INT_MAX / si_a)) - { - Err(ArithmeticError::Overflow) - } else { - Ok(si_a * si_b) - } - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_BgUHiRB4kc4c - :status: draft - - This compliant example uses safe checked addition instead of manual bounds checks. - Checked functions can reduce readability when complex arithmetic expressions are needed. - - .. rust-example:: - - # #[derive(Debug)] - # enum ArithmeticError { Overflow, DivisionByZero } - fn add(si_a: i32, si_b: i32) -> Result { - si_a.checked_add(si_b).ok_or(ArithmeticError::Overflow) - } - - fn sub(a: i32, b: i32) -> Result { - a.checked_sub(b).ok_or(ArithmeticError::Overflow) - } - - fn mul(a: i32, b: i32) -> Result { - a.checked_mul(b).ok_or(ArithmeticError::Overflow) - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_BgUHiRB4kc4b - :status: draft - - Wrapping behavior must be explicitly requested. This compliant example uses wrapping functions. - - .. rust-example:: - - fn add(a: i32, b: i32) -> i32 { - a.wrapping_add(b) - } - - fn sub(a: i32, b: i32) -> i32 { - a.wrapping_sub(b) - } - - fn mul(a: i32, b: i32) -> i32 { - a.wrapping_mul(b) - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_BhUHiRB4kc4b - :status: draft - - Wrapping behavior call also be achieved using the ``Wrapping`` type as in this compliant solution. - The ``Wrapping`` type is a ``struct`` found in the ``std::num`` module that explicitly enables two's complement - wrapping arithmetic for the inner type ``T`` (which must be an integer or ``usize/isize``). - The ``Wrapping`` type provides a consistent way to force wrapping behavior in all build modes, - which is useful in specific scenarios like implementing cryptography or hash functions where wrapping arithmetic is the intended behavior. - - .. rust-example:: - - use std::num::Wrapping; - - fn add(si_a: Wrapping, si_b: Wrapping) -> Wrapping { - si_a + si_b - } - - fn sub(si_a: Wrapping, si_b: Wrapping) -> Wrapping { - si_a - si_b - } - - fn mul(si_a: Wrapping, si_b: Wrapping) -> Wrapping { - si_a * si_b - } - - fn main() { - let si_a = Wrapping(i32::MAX); - let si_b = Wrapping(i32::MAX); - println!("{} + {} = {}", si_a, si_b, add(si_a, si_b)) - } - - .. compliant_example:: - :id: compl_ex_BgUHiSB4kc4b - :status: draft - - Saturation semantics means that instead of wrapping around or resulting in an error, - any result that falls outside the valid range of the integer type is clamped: - - - To the maximum value, if the result were to be greater than the maximum value, or - - To the minimum value, if the result were to be smaller than the minimum, - - Saturation semantics always conform to this rule because they ensure that integer operations do not result in arithmetic overflow. - This compliant solution shows how to use saturating functions to provide saturation semantics for some basic arithmetic operations. - - .. rust-example:: - - fn add(a: i32, b: i32) -> i32 { - a.saturating_add(b) - } - - fn sub(a: i32, b: i32) -> i32 { - a.saturating_sub(b) - } - - fn mul(a: i32, b: i32) -> i32 { - a.saturating_mul(b) - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_BgUHiSB4kd4b - :status: draft - - ``Saturating`` is a wrapper type in Rust's ``core`` library (``core::num::Saturating``) that makes arithmetic operations on the wrapped value perform saturating arithmetic instead of wrapping, panicking, or overflowing. - ``Saturating`` is useful when you have a section of code or a data type where all arithmetic must be saturating. - This compliant solution uses the ``Saturating`` type to define several functions that perform basic integer operations using saturation semantics. - - .. rust-example:: - - use std::num::Saturating; - - fn add(si_a: Saturating, si_b: Saturating) -> Saturating { - si_a + si_b - } - - fn sub(si_a: Saturating, si_b: Saturating) -> Saturating { - si_a - si_b - } - - fn mul(si_a: Saturating, si_b: Saturating) -> Saturating { - si_a * si_b - } - - fn main() { - let si_a = Saturating(i32::MAX); - let si_b = Saturating(i32::MAX); - println!("{} + {} = {}", si_a, si_b, add(si_a, si_b)) - } - - .. non_compliant_example:: - :id: non_compl_ex_cCh2RQUXeH0O - :status: draft - - This noncompliant code example example prevents divide-by-zero errors, but does not prevent arithmetic overflow. - - .. rust-example:: - - # #[derive(Debug)] - # enum DivError { DivisionByZero, Overflow } - fn div(s_a: i64, s_b: i64) -> Result { - if s_b == 0 { - Err(DivError::DivisionByZero) - } else { - Ok(s_a / s_b) - } - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_BgUHiRB4kc4d - :status: draft - - This compliant solution eliminates the possibility of both divide-by-zero errors and arithmetic overflow: - - .. rust-example:: - - # #[derive(Debug)] - # enum DivError { DivisionByZero, Overflow } - fn div(s_a: i64, s_b: i64) -> Result { - if s_b == 0 { - Err(DivError::DivisionByZero) - } else if s_a == i64::MIN && s_b == -1 { - Err(DivError::Overflow) - } else { - Ok(s_a / s_b) - } - } - # - # fn main() {} - -.. guideline:: Avoid as underscore pointer casts - :id: gui_HDnAZ7EZ4z6G - :category: required - :status: draft - :release: - :fls: fls_1qhsun1vyarz - :decidability: decidable - :scope: module - :tags: readability, reduce-human-error - - Code must not rely on Rust's type inference when doing explicit pointer casts via ``var as Type`` or :std:`core::mem::transmute`. - Instead, explicitly specify the complete target type in the ``as`` expression or :std:`core::mem::transmute` call expression. - - .. rationale:: - :id: rat_h8LdJQ1MNKu9 - :status: draft - - ``var as Type`` casts and :std:`core::mem::transmute`\s between raw pointer types are generally valid and unchecked by the compiler as long the target pointer type is a thin pointer. - Not specifying the concrete target pointer type allows the compiler to infer it from the surroundings context which may result in the cast accidentally changing due to surrounding type changes resulting in semantically invalid pointer casts. - - Raw pointers have a variety of invariants to manually keep track of. - Specifying the concrete types in these scenarios allows the compiler to catch some of these potential issues for the user. - - .. non_compliant_example:: - :id: non_compl_ex_V37Pl103aUW4 - :status: draft - - The following code leaves it up to type inference to figure out the concrete types of the raw pointer casts, allowing changes to ``with_base``'s function signature to affect the types the function body of ``non_compliant_example`` without incurring a compiler error. - - .. rust-example:: - - #[repr(C)] - struct Base { - position: (u32, u32) - } - - #[repr(C)] - struct Extended { - base: Base, - scale: f32 - } - - fn non_compliant_example(extended: &Extended) { - let extended = extended as *const _; - with_base(unsafe { &*(extended as *const _) }) - } - - fn with_base(_: &Base) {} - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_W08ckDrkOhkt - :status: draft - - We specify the concrete target types for our pointer casts resulting in a compilation error if the function signature of ``with_base`` is changed. - - .. rust-example:: - - #[repr(C)] - struct Base { - position: (u32, u32) - } - - #[repr(C)] - struct Extended { - base: Base, - scale: f32 - } - - fn compliant_example(extended: &Extended) { - let extended = extended as *const Extended; - with_base(unsafe { &*(extended as *const Base) }) - } - - fn with_base(_: &Base) {} - # - # fn main() {} - -.. guideline:: Do not use an integer type as a divisor during integer division - :id: gui_7y0GAMmtMhch - :category: advisory - :status: draft - :release: latest - :fls: fls_Q9dhNiICGIfr - :decidability: decidable - :scope: module - :tags: numerics, subset - - Do not provide a right operand of - `integer type `_ - during a `division expression - `_ or `remainder expression - `_ when the left operand also has integer type. - - This rule applies to the following primitive integer types: - - * ``i8`` - * ``i16`` - * ``i32`` - * ``i64`` - * ``i128`` - * ``u8`` - * ``u16`` - * ``u32`` - * ``u64`` - * ``u128`` - * ``usize`` - * ``isize`` - - .. rationale:: - :id: rat_vLFlPWSCHRje - :status: draft - - Integer division and integer remainder division both panic when the right operand has a value of zero. - Division by zero is undefined in mathematics because it leads to contradictions and there is no consistent value that can be assigned as its result. - - .. non_compliant_example:: - :id: non_compl_ex_0XeioBrgfh5z - :status: draft - - Both the division and remainder operations in this non-compliant example will panic if evaluated because the right operand is zero. - - .. rust-example:: - :compile_fail: - - fn main() { - let x = 0; - let _y = 5 / x; // This line will panic. - let _z = 5 % x; // This line would also panic. - } - - .. compliant_example:: - :id: compl_ex_k1CD6xoZxhXb - :status: draft - - Checked division prevents division by zero from occurring. - The programmer can then handle the returned :std:`std::option::Option`. - Using checked division and remainder is particularly important in the signed integer case, - where arithmetic overflow can also occur when dividing the minimum representable value by -1. - - .. rust-example:: - - fn main() { - // Using the checked division API - let _y = match 5i32.checked_div(0) { - None => 0, - Some(r) => r, - }; - - // Using the checked remainder API - let _z = match 5i32.checked_rem(0) { - None => 0, - Some(r) => r, - }; - } - - .. compliant_example:: - :id: compl_ex_k1CD6xoZxhXc - :status: draft - - This compliant solution creates a divisor using :std:`std::num::NonZero`. - :std:`std::num::NonZero` is a wrapper around primitive integer types that guarantees the contained value is never zero. - :std:`std::num::NonZero::new` creates a new binding that represents a value that is known not to be zero. - This ensures that functions operating on its value can correctly assume that they are not being given zero as their input. - - Note that the test for arithmetic overflow that occurs when dividing the minimum representable value by -1 is unnecessary - in this compliant example because the result of the division expression is an unsigned integer type. - - .. rust-example:: - :version: 1.79 - - use std::num::NonZero; - - fn main() { - let x = 0u32; - if let Some(divisor) = NonZero::::new(x) { - let _result = 5u32 / divisor; - } - } - -.. guideline:: Do not divide by 0 - :id: gui_kMbiWbn8Z6g5 - :category: required - :status: draft - :release: latest - :fls: fls_Q9dhNiICGIfr - :decidability: undecidable - :scope: system - :tags: numerics, defect - - Integer division by zero results in a panic. - This includes both `division expressions - `_ and `remainder expressions - `_. - - Division and remainder expressions on signed integers are also susceptible to arithmetic overflow. - Overflow is covered in full by the guideline `Ensure that integer operations do not result in arithmetic overflow`. - - This rule applies to the following primitive integer types: - - * ``i8`` - * ``i16`` - * ``i32`` - * ``i64`` - * ``i128`` - * ``u8`` - * ``u16`` - * ``u32`` - * ``u64`` - * ``u128`` - * ``usize`` - * ``isize`` - - This rule does not apply to evaluation of the :std:`core::ops::Div` trait on types other than `integer - types `_. - - This rule is a less strict version of `Do not use an integer type as a divisor during integer division`. - All code that complies with that rule also complies with this rule. - - .. rationale:: - :id: rat_h84NjY2tLSBW - :status: draft - - Integer division by zero results in a panic; an abnormal program state that may terminate the process and must be avoided. - - .. non_compliant_example:: - :id: non_compl_ex_LLs3vY8aGz0F - :status: draft - - This non-compliant example panics when the right operand is zero for either the division or remainder operations. - - .. rust-example:: - :compile_fail: - - fn main() { - let x = 0; - let _y = 5 / x; // Results in a panic. - let _z = 5 % x; // Also results in a panic. - } - - .. compliant_example:: - :id: compl_ex_Ri9pP5Ch3kcc - :status: draft - - Compliant examples from `Do not use an integer type as a divisor during integer division` are also valid for this rule. - Additionally, the check for zero can be performed manually, as in this compliant example. - However, as the complexity of the control flow leading to the invariant increases, - it becomes increasingly harder for both programmers and static analysis tools to reason about it. - - Note that the test for arithmetic overflow is not necessary for unsigned integers. - - .. rust-example:: - - fn main() { - // Checking for zero by hand - let x = 0u32; - let _y = if x != 0u32 { - 5u32 / x - } else { - 0u32 - }; - - let _z = if x != 0u32 { - 5u32 % x - } else { - 0u32 - }; - } - -.. guideline:: The 'as' operator should not be used with numeric operands - :id: gui_ADHABsmK9FXz - :category: advisory - :status: draft - :release: - :fls: fls_otaxe9okhdr1 - :decidability: decidable - :scope: module - :tags: subset, reduce-human-error - - The binary operator ``as`` should not be used with: - - * a numeric type, including all supported integer, floating, and machine-dependent arithmetic types; or - * ``bool``; or - * ``char`` - - as either the right operand or the type of the left operand. - - **Exception:** ``as`` may be used with ``usize`` as the right operand and an expression of raw pointer - type as the left operand. - - .. rationale:: - :id: rat_v56bjjcveLxQ - :status: draft - - Although the conversions performed by ``as`` between numeric types are all well-defined, ``as`` coerces - the value to fit in the destination type, which may result in unexpected data loss if the value needs to - be truncated, rounded, or produce a nearest possible non-equal value. - - Although some conversions are lossless, others are not symmetrical. Instead of relying on either a defined - lossy behaviour or risking loss of precision, the code can communicate intent by using ``Into`` or ``From`` - and ``TryInto`` or ``TryFrom`` to signal which conversions are intended to perfectly preserve the original - value, and which are intended to be fallible. The latter cannot be used from const functions, indicating - that these should avoid using fallible conversions. - - A pointer-to-address cast does not lose value, but will be truncated unless the destination type is large - enough to hold the address value. The ``usize`` type is guaranteed to be wide enough for this purpose. - - A pointer-to-address cast is not symmetrical because the resulting pointer may not point to a valid object, - may not point to an object of the right type, or may not be properly aligned. - If a conversion in this direction is needed, :std:`std::mem::transmute` will communicate the intent to perform - an unsafe operation. - - .. non_compliant_example:: - :id: non_compl_ex_hzGUYoMnK59w - :status: draft - - ``as`` used here can change the value range or lose precision. - Even when it doesn't, nothing enforces the correct behaviour or communicates whether - we intend to allow lossy conversions, or only expect valid conversions. - - .. rust-example:: - - fn f1(x: u16, y: i32, z: u64, w: u8) { - let _a = w as char; // non-compliant - let _b = y as u32; // non-compliant - changes value range, converting negative values - let _c = x as i64; // non-compliant - could use .into() - - let d = y as f32; // non-compliant - lossy - let e = d as f64; // non-compliant - could use .into() - let _f = e as f32; // non-compliant - lossy - - let _g = e as i64; // non-compliant - lossy despite object size - - let b: u32 = 0; - let p1: * const u32 = &b; - let _a1 = p1 as usize; // compliant by exception - let _a2 = p1 as u16; // non-compliant - may lose address range - let _a3 = p1 as u64; // non-compliant - use usize to indicate intent - - let a1 = p1 as usize; - let _p2 = a1 as * const u32; // non-compliant - prefer transmute - let a2 = p1 as u16; - let _p3 = a2 as * const u32; // non-compliant (and most likely not in a valid address range) - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_uilHTIOgxD37 - :status: draft - - Valid conversions that are guaranteed to preserve exact values can be communicated - better with ``into()`` or ``from()``. - Valid conversions that risk losing value, where doing so would be an error, can - communicate this and include an error check, with ``try_into`` or ``try_from``. - Other forms of conversion may find ``transmute`` better communicates their intent. - - .. rust-example:: - - use std::convert::TryInto; - - fn f2(x: u16, y: i32, _z: u64, w: u8) { - let _a: char = w.into(); - let _b: Result = y.try_into(); // produce an error on range clip - let _c: i64 = x.into(); - - let d = f32::from(x); // u16 is within range, u32 is not - let _e = f64::from(d); - // let f = f32::from(e); // no From exists - - // let g = ... // no From exists - - let h: u32 = 0; - let p1: * const u32 = &h; - let a1 = p1 as usize; // (compliant) - - unsafe { - let _a2: usize = std::mem::transmute(p1); // OK - let _a3: u64 = std::mem::transmute(p1); // OK, size is checked - // let a3: u16 = std::mem::transmute(p1); // invalid, different sizes - - let _p2: * const u32 = std::mem::transmute(a1); // OK - let _p3: * const u32 = std::mem::transmute(a1); // OK - } - - unsafe { - // does something entirely different, - // reinterpreting the bits of z as the IEEE bit pattern of a double - // precision object, rather than converting the integer value - let _f1: f64 = std::mem::transmute(_z); - } - } - # - # fn main() {} - - -.. guideline:: An integer shall not be converted to a pointer - :id: gui_PM8Vpf7lZ51U - :category: - :status: draft - :release: - :fls: fls_59mpteeczzo - :decidability: decidable - :scope: module - :tags: subset, undefined-behavior - - The ``as`` operator shall not be used with an expression of numeric type as the left operand, - and any pointer type as the right operand. - - :std:`std::mem::transmute` shall not be used with any numeric type (including floating point types) - as the argument to the ``Src`` parameter, and any pointer type as the argument to the ``Dst`` parameter. - - .. rationale:: - :id: rat_YqhEiWTj9z6L - :status: draft - - A pointer created from an arbitrary arithmetic expression may designate an invalid address, - including an address that does not point to a valid object, an address that points to an - object of the wrong type, or an address that is not properly aligned. Use of such a pointer - to access memory will result in undefined behavior. - - The ``as`` operator also does not check that the size of the source operand is the same as - the size of a pointer, which may lead to unexpected results if the address computation was - originally performed in a differently-sized address space. - - While ``as`` can notionally be used to create a null pointer, the functions - :std:`core::ptr::null` and :std:`core::ptr::null_mut` are the more idiomatic way to do this. - - .. non_compliant_example:: - :id: non_compl_ex_0ydPk7VENSrA - :status: draft - - Any use of ``as`` or ``transmute`` to create a pointer from an arithmetic address value - is non-compliant: - - .. rust-example:: - - fn f1(x: u16, y: i32, z: u64, w: usize) { - let _p1 = x as * const u32; // not compliant - let _p2 = y as * const u32; // not compliant - let _p3 = z as * const u32; // not compliant - let _p4 = w as * const u32; // not compliant despite being the right size - - let _f: f64 = 10.0; - // let p5 = f as * const u32; // not valid - - unsafe { - // let p5: * const u32 = std::mem::transmute(x); // not valid - // let p6: * const u32 = std::mem::transmute(y); // not valid - - let _p7: * const u32 = std::mem::transmute(z); // not compliant - let _p8: * const u32 = std::mem::transmute(w); // not compliant - - let _p9: * const u32 = std::mem::transmute(_f); // not compliant, and very strange - } - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_oneKuF52yzrx - :status: draft - - There is no compliant example of this operation. - -.. guideline:: An integer shall not be converted to an invalid pointer - :id: gui_iv9yCMHRgpE0 - :category: - :status: draft - :release: - :fls: fls_9wgldua1u8yt - :decidability: undecidable - :scope: system - :tags: defect, undefined-behavior - - An expression of numeric type shall not be converted to a pointer if the resulting pointer - is incorrectly aligned, does not point to an entity of the referenced type, or is an invalid representation. - - .. rationale:: - :id: rat_OhxKm751axKw - :status: draft - - The mapping between pointers and integers must be consistent with the addressing structure of the - execution environment. Issues may arise, for example, on architectures that have a segmented memory model. - - .. non_compliant_example:: - :id: non_compl_ex_CkytKjRQezfQ - :status: draft - - This example makes assumptions about the layout of the address space that do not hold on all platforms. - The manipulated address may have discarded part of the original address space, and the flag may - silently interfere with the address value. On platforms where pointers are 64-bits this may have - particularly unexpected results. - - .. rust-example:: - - fn f1(flag: u32, ptr: * const u32) { - /* ... */ - let mut rep = ptr as usize; - rep = (rep & 0x7fffff) | ((flag as usize) << 23); - let _p2 = rep as * const u32; - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_oBoluiKSvREu - :status: draft - - This compliant solution uses a struct to provide storage for both the pointer and the flag value. - This solution is portable to machines of different word sizes, both smaller and larger than 32 bits, - working even when pointers cannot be represented in any integer type. - - .. rust-example:: - - struct PtrFlag { - pointer: * const u32, - flag: u32 - } - - fn f2(flag: u32, ptr: * const u32) { - let _ptrflag = PtrFlag { - pointer: ptr, - flag: flag - }; - /* ... */ - } - # - # fn main() {} - -.. guideline:: Do not shift an expression by a negative number of bits or by greater than or equal to the bitwidth of the operand - :id: gui_RHvQj8BHlz9b - :category: advisory - :status: draft - :release: 1.7.0-latest - :fls: fls_sru4wi5jomoe - :decidability: decidable - :scope: module - :tags: numerics, reduce-human-error, maintainability, surprising-behavior, subset - - Shifting negative positions or a value greater than or equal to the width of the left operand - in `shift left and shift right expressions `_ - are defined by this guideline to be *out-of-range shifts*. - The Rust FLS incorrectly describes this behavior as <`arithmetic overflow `__. - - If the types of both operands are integer types, - the shift left expression ``lhs << rhs`` evaluates to the value of the left operand ``lhs`` whose bits are - shifted left by the number of positions specified by the right operand ``rhs``. - Vacated bits are filled with zeros. - The expression ``lhs << rhs`` evaluates to :math:`\mathrm{lhs} \times 2^{\mathrm{rhs}}`, - cast to the type of the left operand. - If the value of the right operand is negative or greater than or equal to the width of the left operand, - then the operation results in an out-of-range shift. - - If the types of both operands are integer types, - the shift right expression ``lhs >> rhs`` evaluates to the value of the left operand ``lhs`` - whose bits are shifted right by the number of positions specified by the right operand ``rhs``. - If the type of the left operand is any signed integer type and is negative, - the vacated bits are filled with ones. - Otherwise, vacated bits are filled with zeros. - The expression ``lhs >> rhs`` evaluates to :math:`\mathrm{lhs} / 2^{\mathrm{rhs}}`, - cast to the type of the left operand. - If the value of the right operand is negative, - greater than or equal to the width of the left operand, - then the operation results in an out-of-range shift. - - This rule applies to the following primitive types: - - * ``i8`` - * ``i16`` - * ``i32`` - * ``i64`` - * ``i128`` - * ``u8`` - * ``u16`` - * ``u32`` - * ``u64`` - * ``u128`` - * ``usize`` - * ``isize`` - - Any type can support ``<<`` or ``>>`` if you implement the trait: - - .. rust-example:: - - use core::ops::Shl; - # struct MyType; - - impl Shl for MyType { - type Output = MyType; - fn shl(self, _rhs: u32) -> Self::Output { MyType } - } - # - # fn main() {} - - You may choose any type for the right operand (not just integers), because you control the implementation. - - This rule is based on The CERT C Coding Standard Rule - `INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the left operand `_. - - .. rationale:: - :id: rat_3MpR8QfHodGT - :status: draft - - Avoid out-of-range shifts in shift left and shift right expressions. - Shifting by a negative value, or by a value greater than or equal to the width of the left operand - are non-sensical expressions which typically indicate a logic error has occurred. - - .. non_compliant_example:: - :id: non_compl_ex_O9FZuazu3Lcn - :status: draft - - This noncompliant example shifts by a negative value (-1) and also by greater than or equal to the number of bits that exist in the left operand (40):. - - .. rust-example:: - - fn main() { - let bits : u32 = 61; - let shifts = vec![-1, 4, 40]; - - for sh in shifts { - println!("{bits} << {sh} = {:?}", bits << sh); - } - } - - .. non_compliant_example:: - :id: non_compl_ex_mvkgTL3kulZ5 - :status: draft - - This noncompliant example test the value of ``sh`` to ensure the value of the right operand is negative or greater - than or equal to the width of the left operand. - - .. rust-example:: - - fn main() { - let bits: u32 = 61; - let shifts = vec![-1, 0, 4, 40]; - - for sh in shifts { - if sh >= 0 && sh < 32 { - println!("{bits} << {sh} = {}", bits << sh); - } - } - } - - .. non_compliant_example:: - :id: non_compl_ex_O9FZuazu3Lcm - :status: draft - - The call to ``bits.wrapping_shl(sh)`` in this noncompliant example yields ``bits << mask(sh)``, - where ``mask`` removes any high-order bits of ``sh`` that would cause the shift to exceed the bitwidth of ``bits``. - Note that this is not the same as a rotate-left. - The ``wrapping_shl`` has the same behavior as the ``<<`` operator in release mode. - - .. rust-example:: - - fn main() { - let bits : u32 = 61; - let shifts = vec![4, 40]; - - for sh in shifts { - println!("{bits} << {sh} = {:?}", bits.wrapping_shl(sh)); - } - } - - .. non_compliant_example:: - :id: non_compl_ex_O9FZuazu3Lcx - :status: draft - - This noncompliant example uses ``bits.unbounded_shr(sh)``. - If ``sh`` is larger or equal to the width of ``bits``, - the entire value is shifted out, - which yields 0 for a positive number, - and -1 for a negative number. - The use of this function is noncompliant because it does not detect out-of-range shifts. - - .. rust-example:: - :version: 1.87 - - fn main() { - let bits : u32 = 61; - let shifts = vec![4, 40]; - - for sh in shifts { - println!("{bits} << {sh} = {:?}", bits.unbounded_shr(sh)); - } - } - - .. non_compliant_example:: - :id: non_compl_ex_O9FZuazu3Lcp - :status: draft - - The call to ``bits.overflowing_shl(sh)`` in this noncompliant shifts ``bits`` left by ``sh`` bits. - Returns a tuple of the shifted version of self along with a boolean indicating whether the shift value was larger than or equal to the number of bits. - If the shift value is too large, then value is masked (N-1) where N is the number of bits, and this value is used to perform the shift. - - .. rust-example:: - - fn main() { - let bits: u32 = 61; - let shifts = vec![4, 40]; - - for sh in shifts { - let (result, overflowed) = bits.overflowing_shl(sh); - if overflowed { - println!("{bits} << {sh} shift too large"); - } else { - println!("{bits} << {sh} = {result}"); - } - } - } - - .. compliant_example:: - :id: compl_ex_xpPQqYeEPGIo - :status: draft - - This compliant example performs left shifts via the `checked_shl `_ - function and right shifts via the `checked_shr `_ function. - Both of these functions are defined in `core `_. - - ``::checked_shl(M)`` returns a value of type ``Option``: - - * If ``M < 0``\ , the output is ``None`` - * If ``0 <= M < N`` for ``T`` of size ``N`` bits, then the output is ``Some(T)`` - * If ``N <= M``\ , the output is ``None`` - - Checked shift operations make programmer intent explicit and eliminates out-of-range shifts. - Shifting by: - - * negative values is impossible because ``checked_shl`` only accepts unsigned integers as shift lengths, and - * greater than or equal to the number of bits that exist in the left operand returns a ``None`` value. - - .. rust-example:: - - fn main() { - let bits : u32 = 61; - // let shifts = vec![-1, 4, 40]; - // ^--- Compiler rejects negative shifts - let shifts = vec![4, 40]; - - for sh in shifts { - println!("{bits} << {sh} = {:?}", bits.checked_shl(sh)); - } - } - -.. guideline:: Avoid out-or-range shifts - :id: gui_LvmzGKdsAgI5 - :category: mandatory - :status: draft - :release: 1.0.0-latest - :fls: fls_sru4wi5jomoe - :decidability: undecidable - :scope: module - :tags: numerics, surprising-behavior, defect - - Shifting negative positions or a value greater than or equal to the width of the left operand - in `shift left and shift right expressions `_ - are defined by this guideline to be *out-of-range shifts*. - The Rust FLS incorrectly describes this behavior as <`arithmetic overflow `_. - - If the types of both operands are integer types, - the shift left expression ``lhs << rhs`` evaluates to the value of the left operand ``lhs`` whose bits are - shifted left by the number of positions specified by the right operand ``rhs``. - Vacated bits are filled with zeros. - The expression ``lhs << rhs`` evaluates to :math:`\mathrm{lhs} \times 2^{\mathrm{rhs}}`, - cast to the type of the left operand. - If the value of the right operand is negative or greater than or equal to the width of the left operand, - then the operation results in an out-of-range shift. - - If the types of both operands are integer types, - the shift right expression ``lhs >> rhs`` evaluates to the value of the left operand ``lhs`` - whose bits are shifted right by the number of positions specified by the right operand ``rhs``. - If the type of the left operand is any signed integer type and is negative, - the vacated bits are filled with ones. - Otherwise, vacated bits are filled with zeros. - The expression ``lhs >> rhs`` evaluates to :math:`\mathrm{lhs} / 2^{\mathrm{rhs}}`, - cast to the type of the left operand. - If the value of the right operand is negative, - greater than or equal to the width of the left operand, - then the operation results in an out-of-range shift. - - This rule applies to the following primitive types: - - * ``i8`` - * ``i16`` - * ``i32`` - * ``i64`` - * ``i128`` - * ``u8`` - * ``u16`` - * ``u32`` - * ``u64`` - * ``u128`` - * ``usize`` - * ``isize`` - - Any type can support ``<<`` or ``>>`` if you implement the trait: - - .. rust-example:: - - use core::ops::Shl; - # struct MyType; - - impl Shl for MyType { - type Output = MyType; - fn shl(self, _rhs: u32) -> Self::Output { MyType } - } - # - # fn main() {} - - You may choose any type for the right operand (not just integers), because you control the implementation. - - This rule is a less strict but undecidable version of - `Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand`. - All code that complies with that rule also complies with this rule. - - This rule is based on The CERT C Coding Standard Rule - `INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the left operand `_. - - .. rationale:: - :id: rat_tVkDl6gOqz25 - :status: draft - - Avoid out-of-range shifts in shift left and shift right expressions. - Shifting by a negative value, or by a value greater than or equal to the width of the left operand - are non-sensical expressions which typically indicate a logic error has occurred. - - .. non_compliant_example:: - :id: non_compl_ex_KLiDMsCesLx7 - :status: draft - - This noncompliant example shifts by a negative value (-1) and also by greater than or equal to the number of bits that exist in the left operand (40):. - - .. rust-example:: - - fn main() { - let bits : u32 = 61; - let shifts = vec![-1, 4, 40]; - - for sh in shifts { - println!("{bits} << {sh} = {:?}", bits << sh); - } - } - - .. compliant_example:: - :id: compl_ex_Ux1WqHbGKV73 - :status: draft - - This compliant example test the value of ``sh`` to ensure the value of the right operand is negative or greater - than or equal to the width of the left operand. - - .. rust-example:: - - fn main() { - let bits: u32 = 61; - let shifts = vec![-1, 0, 4, 40]; - - for sh in shifts { - if sh >= 0 && sh < 32 { - println!("{bits} << {sh} = {}", bits << sh); - } - } - } - - .. compliant_example:: - :id: compl_ex_Ux1WqHbGKV74 - :status: draft - - The call to ``bits.overflowing_shl(sh)`` in this noncompliant shifts ``bits`` left by ``sh`` bits. - Returns a tuple of the shifted version of self along with a boolean indicating whether the shift value was larger than or equal to the number of bits. - If the shift value is too large, then value is masked (N-1) where N is the number of bits, and this value is used to perform the shift. - - .. rust-example:: - - fn safe_shl(bits: u32, shift: u32) -> u32 { - let (result, overflowed) = bits.overflowing_shl(shift); - if overflowed { - 0 - } else { - result - } - } - - fn main() { - let bits: u32 = 61; - let shifts = vec![4, 40]; - - for sh in shifts { - let result = safe_shl(bits, sh); - println!("{bits} << {sh} = {result}"); - } - } diff --git a/src/coding-guidelines/expressions/gui_7y0GAMmtMhch.rst.inc b/src/coding-guidelines/expressions/gui_7y0GAMmtMhch.rst.inc new file mode 100644 index 00000000..19a6b0cb --- /dev/null +++ b/src/coding-guidelines/expressions/gui_7y0GAMmtMhch.rst.inc @@ -0,0 +1,106 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Do not use an integer type as a divisor during integer division + :id: gui_7y0GAMmtMhch + :category: advisory + :status: draft + :release: latest + :fls: fls_Q9dhNiICGIfr + :decidability: decidable + :scope: module + :tags: numerics, subset + + Do not provide a right operand of + `integer type `_ + during a `division expression + `_ or `remainder expression + `_ when the left operand also has integer type. + + This rule applies to the following primitive integer types: + + * ``i8`` + * ``i16`` + * ``i32`` + * ``i64`` + * ``i128`` + * ``u8`` + * ``u16`` + * ``u32`` + * ``u64`` + * ``u128`` + * ``usize`` + * ``isize`` + + .. rationale:: + :id: rat_vLFlPWSCHRje + :status: draft + + Integer division and integer remainder division both panic when the right operand has a value of zero. + Division by zero is undefined in mathematics because it leads to contradictions and there is no consistent value that can be assigned as its result. + + .. non_compliant_example:: + :id: non_compl_ex_0XeioBrgfh5z + :status: draft + + Both the division and remainder operations in this non-compliant example will panic if evaluated because the right operand is zero. + + .. rust-example:: + :compile_fail: + + fn main() { + let x = 0; + let _y = 5 / x; // This line will panic. + let _z = 5 % x; // This line would also panic. + } + + .. compliant_example:: + :id: compl_ex_k1CD6xoZxhXb + :status: draft + + Checked division prevents division by zero from occurring. + The programmer can then handle the returned :std:`std::option::Option`. + Using checked division and remainder is particularly important in the signed integer case, + where arithmetic overflow can also occur when dividing the minimum representable value by -1. + + .. rust-example:: + + fn main() { + // Using the checked division API + let _y = match 5i32.checked_div(0) { + None => 0, + Some(r) => r, + }; + + // Using the checked remainder API + let _z = match 5i32.checked_rem(0) { + None => 0, + Some(r) => r, + }; + } + + .. compliant_example:: + :id: compl_ex_k1CD6xoZxhXc + :status: draft + + This compliant solution creates a divisor using :std:`std::num::NonZero`. + :std:`std::num::NonZero` is a wrapper around primitive integer types that guarantees the contained value is never zero. + :std:`std::num::NonZero::new` creates a new binding that represents a value that is known not to be zero. + This ensures that functions operating on its value can correctly assume that they are not being given zero as their input. + + Note that the test for arithmetic overflow that occurs when dividing the minimum representable value by -1 is unnecessary + in this compliant example because the result of the division expression is an unsigned integer type. + + .. rust-example:: + :version: 1.79 + + use std::num::NonZero; + + fn main() { + let x = 0u32; + if let Some(divisor) = NonZero::::new(x) { + let _result = 5u32 / divisor; + } + } diff --git a/src/coding-guidelines/expressions/gui_ADHABsmK9FXz.rst.inc b/src/coding-guidelines/expressions/gui_ADHABsmK9FXz.rst.inc new file mode 100644 index 00000000..b6a2d785 --- /dev/null +++ b/src/coding-guidelines/expressions/gui_ADHABsmK9FXz.rst.inc @@ -0,0 +1,130 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: The 'as' operator should not be used with numeric operands + :id: gui_ADHABsmK9FXz + :category: advisory + :status: draft + :release: + :fls: fls_otaxe9okhdr1 + :decidability: decidable + :scope: module + :tags: subset, reduce-human-error + + The binary operator ``as`` should not be used with: + + * a numeric type, including all supported integer, floating, and machine-dependent arithmetic types; or + * ``bool``; or + * ``char`` + + as either the right operand or the type of the left operand. + + **Exception:** ``as`` may be used with ``usize`` as the right operand and an expression of raw pointer + type as the left operand. + + .. rationale:: + :id: rat_v56bjjcveLxQ + :status: draft + + Although the conversions performed by ``as`` between numeric types are all well-defined, ``as`` coerces + the value to fit in the destination type, which may result in unexpected data loss if the value needs to + be truncated, rounded, or produce a nearest possible non-equal value. + + Although some conversions are lossless, others are not symmetrical. Instead of relying on either a defined + lossy behaviour or risking loss of precision, the code can communicate intent by using ``Into`` or ``From`` + and ``TryInto`` or ``TryFrom`` to signal which conversions are intended to perfectly preserve the original + value, and which are intended to be fallible. The latter cannot be used from const functions, indicating + that these should avoid using fallible conversions. + + A pointer-to-address cast does not lose value, but will be truncated unless the destination type is large + enough to hold the address value. The ``usize`` type is guaranteed to be wide enough for this purpose. + + A pointer-to-address cast is not symmetrical because the resulting pointer may not point to a valid object, + may not point to an object of the right type, or may not be properly aligned. + If a conversion in this direction is needed, :std:`std::mem::transmute` will communicate the intent to perform + an unsafe operation. + + .. non_compliant_example:: + :id: non_compl_ex_hzGUYoMnK59w + :status: draft + + ``as`` used here can change the value range or lose precision. + Even when it doesn't, nothing enforces the correct behaviour or communicates whether + we intend to allow lossy conversions, or only expect valid conversions. + + .. rust-example:: + + fn f1(x: u16, y: i32, z: u64, w: u8) { + let _a = w as char; // non-compliant + let _b = y as u32; // non-compliant - changes value range, converting negative values + let _c = x as i64; // non-compliant - could use .into() + + let d = y as f32; // non-compliant - lossy + let e = d as f64; // non-compliant - could use .into() + let _f = e as f32; // non-compliant - lossy + + let _g = e as i64; // non-compliant - lossy despite object size + + let b: u32 = 0; + let p1: * const u32 = &b; + let _a1 = p1 as usize; // compliant by exception + let _a2 = p1 as u16; // non-compliant - may lose address range + let _a3 = p1 as u64; // non-compliant - use usize to indicate intent + + let a1 = p1 as usize; + let _p2 = a1 as * const u32; // non-compliant - prefer transmute + let a2 = p1 as u16; + let _p3 = a2 as * const u32; // non-compliant (and most likely not in a valid address range) + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_uilHTIOgxD37 + :status: draft + + Valid conversions that are guaranteed to preserve exact values can be communicated + better with ``into()`` or ``from()``. + Valid conversions that risk losing value, where doing so would be an error, can + communicate this and include an error check, with ``try_into`` or ``try_from``. + Other forms of conversion may find ``transmute`` better communicates their intent. + + .. rust-example:: + + use std::convert::TryInto; + + fn f2(x: u16, y: i32, _z: u64, w: u8) { + let _a: char = w.into(); + let _b: Result = y.try_into(); // produce an error on range clip + let _c: i64 = x.into(); + + let d = f32::from(x); // u16 is within range, u32 is not + let _e = f64::from(d); + // let f = f32::from(e); // no From exists + + // let g = ... // no From exists + + let h: u32 = 0; + let p1: * const u32 = &h; + let a1 = p1 as usize; // (compliant) + + unsafe { + let _a2: usize = std::mem::transmute(p1); // OK + let _a3: u64 = std::mem::transmute(p1); // OK, size is checked + // let a3: u16 = std::mem::transmute(p1); // invalid, different sizes + + let _p2: * const u32 = std::mem::transmute(a1); // OK + let _p3: * const u32 = std::mem::transmute(a1); // OK + } + + unsafe { + // does something entirely different, + // reinterpreting the bits of z as the IEEE bit pattern of a double + // precision object, rather than converting the integer value + let _f1: f64 = std::mem::transmute(_z); + } + } + # + # fn main() {} diff --git a/src/coding-guidelines/expressions/gui_HDnAZ7EZ4z6G.rst.inc b/src/coding-guidelines/expressions/gui_HDnAZ7EZ4z6G.rst.inc new file mode 100644 index 00000000..d0e6017a --- /dev/null +++ b/src/coding-guidelines/expressions/gui_HDnAZ7EZ4z6G.rst.inc @@ -0,0 +1,83 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Avoid as underscore pointer casts + :id: gui_HDnAZ7EZ4z6G + :category: required + :status: draft + :release: + :fls: fls_1qhsun1vyarz + :decidability: decidable + :scope: module + :tags: readability, reduce-human-error + + Code must not rely on Rust's type inference when doing explicit pointer casts via ``var as Type`` or :std:`core::mem::transmute`. + Instead, explicitly specify the complete target type in the ``as`` expression or :std:`core::mem::transmute` call expression. + + .. rationale:: + :id: rat_h8LdJQ1MNKu9 + :status: draft + + ``var as Type`` casts and :std:`core::mem::transmute`\s between raw pointer types are generally valid and unchecked by the compiler as long the target pointer type is a thin pointer. + Not specifying the concrete target pointer type allows the compiler to infer it from the surroundings context which may result in the cast accidentally changing due to surrounding type changes resulting in semantically invalid pointer casts. + + Raw pointers have a variety of invariants to manually keep track of. + Specifying the concrete types in these scenarios allows the compiler to catch some of these potential issues for the user. + + .. non_compliant_example:: + :id: non_compl_ex_V37Pl103aUW4 + :status: draft + + The following code leaves it up to type inference to figure out the concrete types of the raw pointer casts, allowing changes to ``with_base``'s function signature to affect the types the function body of ``non_compliant_example`` without incurring a compiler error. + + .. rust-example:: + + #[repr(C)] + struct Base { + position: (u32, u32) + } + + #[repr(C)] + struct Extended { + base: Base, + scale: f32 + } + + fn non_compliant_example(extended: &Extended) { + let extended = extended as *const _; + with_base(unsafe { &*(extended as *const _) }) + } + + fn with_base(_: &Base) {} + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_W08ckDrkOhkt + :status: draft + + We specify the concrete target types for our pointer casts resulting in a compilation error if the function signature of ``with_base`` is changed. + + .. rust-example:: + + #[repr(C)] + struct Base { + position: (u32, u32) + } + + #[repr(C)] + struct Extended { + base: Base, + scale: f32 + } + + fn compliant_example(extended: &Extended) { + let extended = extended as *const Extended; + with_base(unsafe { &*(extended as *const Base) }) + } + + fn with_base(_: &Base) {} + # + # fn main() {} diff --git a/src/coding-guidelines/expressions/gui_LvmzGKdsAgI5.rst.inc b/src/coding-guidelines/expressions/gui_LvmzGKdsAgI5.rst.inc new file mode 100644 index 00000000..71865f9b --- /dev/null +++ b/src/coding-guidelines/expressions/gui_LvmzGKdsAgI5.rst.inc @@ -0,0 +1,152 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Avoid out-or-range shifts + :id: gui_LvmzGKdsAgI5 + :category: mandatory + :status: draft + :release: 1.0.0-latest + :fls: fls_sru4wi5jomoe + :decidability: undecidable + :scope: module + :tags: numerics, surprising-behavior, defect + + Shifting negative positions or a value greater than or equal to the width of the left operand + in `shift left and shift right expressions `_ + are defined by this guideline to be *out-of-range shifts*. + The Rust FLS incorrectly describes this behavior as <`arithmetic overflow `_. + + If the types of both operands are integer types, + the shift left expression ``lhs << rhs`` evaluates to the value of the left operand ``lhs`` whose bits are + shifted left by the number of positions specified by the right operand ``rhs``. + Vacated bits are filled with zeros. + The expression ``lhs << rhs`` evaluates to :math:`\mathrm{lhs} \times 2^{\mathrm{rhs}}`, + cast to the type of the left operand. + If the value of the right operand is negative or greater than or equal to the width of the left operand, + then the operation results in an out-of-range shift. + + If the types of both operands are integer types, + the shift right expression ``lhs >> rhs`` evaluates to the value of the left operand ``lhs`` + whose bits are shifted right by the number of positions specified by the right operand ``rhs``. + If the type of the left operand is any signed integer type and is negative, + the vacated bits are filled with ones. + Otherwise, vacated bits are filled with zeros. + The expression ``lhs >> rhs`` evaluates to :math:`\mathrm{lhs} / 2^{\mathrm{rhs}}`, + cast to the type of the left operand. + If the value of the right operand is negative, + greater than or equal to the width of the left operand, + then the operation results in an out-of-range shift. + + This rule applies to the following primitive types: + + * ``i8`` + * ``i16`` + * ``i32`` + * ``i64`` + * ``i128`` + * ``u8`` + * ``u16`` + * ``u32`` + * ``u64`` + * ``u128`` + * ``usize`` + * ``isize`` + + Any type can support ``<<`` or ``>>`` if you implement the trait: + + .. rust-example:: + + use core::ops::Shl; + # struct MyType; + + impl Shl for MyType { + type Output = MyType; + fn shl(self, _rhs: u32) -> Self::Output { MyType } + } + # + # fn main() {} + + You may choose any type for the right operand (not just integers), because you control the implementation. + + This rule is a less strict but undecidable version of + `Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand`. + All code that complies with that rule also complies with this rule. + + This rule is based on The CERT C Coding Standard Rule + `INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the left operand `_. + + .. rationale:: + :id: rat_tVkDl6gOqz25 + :status: draft + + Avoid out-of-range shifts in shift left and shift right expressions. + Shifting by a negative value, or by a value greater than or equal to the width of the left operand + are non-sensical expressions which typically indicate a logic error has occurred. + + .. non_compliant_example:: + :id: non_compl_ex_KLiDMsCesLx7 + :status: draft + + This noncompliant example shifts by a negative value (-1) and also by greater than or equal to the number of bits that exist in the left operand (40):. + + .. rust-example:: + + fn main() { + let bits : u32 = 61; + let shifts = vec![-1, 4, 40]; + + for sh in shifts { + println!("{bits} << {sh} = {:?}", bits << sh); + } + } + + .. compliant_example:: + :id: compl_ex_Ux1WqHbGKV73 + :status: draft + + This compliant example test the value of ``sh`` to ensure the value of the right operand is negative or greater + than or equal to the width of the left operand. + + .. rust-example:: + + fn main() { + let bits: u32 = 61; + let shifts = vec![-1, 0, 4, 40]; + + for sh in shifts { + if sh >= 0 && sh < 32 { + println!("{bits} << {sh} = {}", bits << sh); + } + } + } + + .. compliant_example:: + :id: compl_ex_Ux1WqHbGKV74 + :status: draft + + The call to ``bits.overflowing_shl(sh)`` in this noncompliant shifts ``bits`` left by ``sh`` bits. + Returns a tuple of the shifted version of self along with a boolean indicating whether the shift value was larger than or equal to the number of bits. + If the shift value is too large, then value is masked (N-1) where N is the number of bits, and this value is used to perform the shift. + + .. rust-example:: + + fn safe_shl(bits: u32, shift: u32) -> u32 { + let (result, overflowed) = bits.overflowing_shl(shift); + if overflowed { + 0 + } else { + result + } + } + + fn main() { + let bits: u32 = 61; + let shifts = vec![4, 40]; + + for sh in shifts { + let result = safe_shl(bits, sh); + println!("{bits} << {sh} = {result}"); + } + } diff --git a/src/coding-guidelines/expressions/gui_PM8Vpf7lZ51U.rst.inc b/src/coding-guidelines/expressions/gui_PM8Vpf7lZ51U.rst.inc new file mode 100644 index 00000000..fd704c24 --- /dev/null +++ b/src/coding-guidelines/expressions/gui_PM8Vpf7lZ51U.rst.inc @@ -0,0 +1,73 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: An integer shall not be converted to a pointer + :id: gui_PM8Vpf7lZ51U + :category: + :status: draft + :release: + :fls: fls_59mpteeczzo + :decidability: decidable + :scope: module + :tags: subset, undefined-behavior + + The ``as`` operator shall not be used with an expression of numeric type as the left operand, + and any pointer type as the right operand. + + :std:`std::mem::transmute` shall not be used with any numeric type (including floating point types) + as the argument to the ``Src`` parameter, and any pointer type as the argument to the ``Dst`` parameter. + + .. rationale:: + :id: rat_YqhEiWTj9z6L + :status: draft + + A pointer created from an arbitrary arithmetic expression may designate an invalid address, + including an address that does not point to a valid object, an address that points to an + object of the wrong type, or an address that is not properly aligned. Use of such a pointer + to access memory will result in undefined behavior. + + The ``as`` operator also does not check that the size of the source operand is the same as + the size of a pointer, which may lead to unexpected results if the address computation was + originally performed in a differently-sized address space. + + While ``as`` can notionally be used to create a null pointer, the functions + :std:`core::ptr::null` and :std:`core::ptr::null_mut` are the more idiomatic way to do this. + + .. non_compliant_example:: + :id: non_compl_ex_0ydPk7VENSrA + :status: draft + + Any use of ``as`` or ``transmute`` to create a pointer from an arithmetic address value + is non-compliant: + + .. rust-example:: + + fn f1(x: u16, y: i32, z: u64, w: usize) { + let _p1 = x as * const u32; // not compliant + let _p2 = y as * const u32; // not compliant + let _p3 = z as * const u32; // not compliant + let _p4 = w as * const u32; // not compliant despite being the right size + + let _f: f64 = 10.0; + // let p5 = f as * const u32; // not valid + + unsafe { + // let p5: * const u32 = std::mem::transmute(x); // not valid + // let p6: * const u32 = std::mem::transmute(y); // not valid + + let _p7: * const u32 = std::mem::transmute(z); // not compliant + let _p8: * const u32 = std::mem::transmute(w); // not compliant + + let _p9: * const u32 = std::mem::transmute(_f); // not compliant, and very strange + } + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_oneKuF52yzrx + :status: draft + + There is no compliant example of this operation. diff --git a/src/coding-guidelines/expressions/gui_RHvQj8BHlz9b.rst.inc b/src/coding-guidelines/expressions/gui_RHvQj8BHlz9b.rst.inc new file mode 100644 index 00000000..eed16f99 --- /dev/null +++ b/src/coding-guidelines/expressions/gui_RHvQj8BHlz9b.rst.inc @@ -0,0 +1,219 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Do not shift an expression by a negative number of bits or by greater than or equal to the bitwidth of the operand + :id: gui_RHvQj8BHlz9b + :category: advisory + :status: draft + :release: 1.7.0-latest + :fls: fls_sru4wi5jomoe + :decidability: decidable + :scope: module + :tags: numerics, reduce-human-error, maintainability, surprising-behavior, subset + + Shifting negative positions or a value greater than or equal to the width of the left operand + in `shift left and shift right expressions `_ + are defined by this guideline to be *out-of-range shifts*. + The Rust FLS incorrectly describes this behavior as <`arithmetic overflow `__. + + If the types of both operands are integer types, + the shift left expression ``lhs << rhs`` evaluates to the value of the left operand ``lhs`` whose bits are + shifted left by the number of positions specified by the right operand ``rhs``. + Vacated bits are filled with zeros. + The expression ``lhs << rhs`` evaluates to :math:`\mathrm{lhs} \times 2^{\mathrm{rhs}}`, + cast to the type of the left operand. + If the value of the right operand is negative or greater than or equal to the width of the left operand, + then the operation results in an out-of-range shift. + + If the types of both operands are integer types, + the shift right expression ``lhs >> rhs`` evaluates to the value of the left operand ``lhs`` + whose bits are shifted right by the number of positions specified by the right operand ``rhs``. + If the type of the left operand is any signed integer type and is negative, + the vacated bits are filled with ones. + Otherwise, vacated bits are filled with zeros. + The expression ``lhs >> rhs`` evaluates to :math:`\mathrm{lhs} / 2^{\mathrm{rhs}}`, + cast to the type of the left operand. + If the value of the right operand is negative, + greater than or equal to the width of the left operand, + then the operation results in an out-of-range shift. + + This rule applies to the following primitive types: + + * ``i8`` + * ``i16`` + * ``i32`` + * ``i64`` + * ``i128`` + * ``u8`` + * ``u16`` + * ``u32`` + * ``u64`` + * ``u128`` + * ``usize`` + * ``isize`` + + Any type can support ``<<`` or ``>>`` if you implement the trait: + + .. rust-example:: + + use core::ops::Shl; + # struct MyType; + + impl Shl for MyType { + type Output = MyType; + fn shl(self, _rhs: u32) -> Self::Output { MyType } + } + # + # fn main() {} + + You may choose any type for the right operand (not just integers), because you control the implementation. + + This rule is based on The CERT C Coding Standard Rule + `INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the left operand `_. + + .. rationale:: + :id: rat_3MpR8QfHodGT + :status: draft + + Avoid out-of-range shifts in shift left and shift right expressions. + Shifting by a negative value, or by a value greater than or equal to the width of the left operand + are non-sensical expressions which typically indicate a logic error has occurred. + + .. non_compliant_example:: + :id: non_compl_ex_O9FZuazu3Lcn + :status: draft + + This noncompliant example shifts by a negative value (-1) and also by greater than or equal to the number of bits that exist in the left operand (40):. + + .. rust-example:: + + fn main() { + let bits : u32 = 61; + let shifts = vec![-1, 4, 40]; + + for sh in shifts { + println!("{bits} << {sh} = {:?}", bits << sh); + } + } + + .. non_compliant_example:: + :id: non_compl_ex_mvkgTL3kulZ5 + :status: draft + + This noncompliant example test the value of ``sh`` to ensure the value of the right operand is negative or greater + than or equal to the width of the left operand. + + .. rust-example:: + + fn main() { + let bits: u32 = 61; + let shifts = vec![-1, 0, 4, 40]; + + for sh in shifts { + if sh >= 0 && sh < 32 { + println!("{bits} << {sh} = {}", bits << sh); + } + } + } + + .. non_compliant_example:: + :id: non_compl_ex_O9FZuazu3Lcm + :status: draft + + The call to ``bits.wrapping_shl(sh)`` in this noncompliant example yields ``bits << mask(sh)``, + where ``mask`` removes any high-order bits of ``sh`` that would cause the shift to exceed the bitwidth of ``bits``. + Note that this is not the same as a rotate-left. + The ``wrapping_shl`` has the same behavior as the ``<<`` operator in release mode. + + .. rust-example:: + + fn main() { + let bits : u32 = 61; + let shifts = vec![4, 40]; + + for sh in shifts { + println!("{bits} << {sh} = {:?}", bits.wrapping_shl(sh)); + } + } + + .. non_compliant_example:: + :id: non_compl_ex_O9FZuazu3Lcx + :status: draft + + This noncompliant example uses ``bits.unbounded_shr(sh)``. + If ``sh`` is larger or equal to the width of ``bits``, + the entire value is shifted out, + which yields 0 for a positive number, + and -1 for a negative number. + The use of this function is noncompliant because it does not detect out-of-range shifts. + + .. rust-example:: + :version: 1.87 + + fn main() { + let bits : u32 = 61; + let shifts = vec![4, 40]; + + for sh in shifts { + println!("{bits} << {sh} = {:?}", bits.unbounded_shr(sh)); + } + } + + .. non_compliant_example:: + :id: non_compl_ex_O9FZuazu3Lcp + :status: draft + + The call to ``bits.overflowing_shl(sh)`` in this noncompliant shifts ``bits`` left by ``sh`` bits. + Returns a tuple of the shifted version of self along with a boolean indicating whether the shift value was larger than or equal to the number of bits. + If the shift value is too large, then value is masked (N-1) where N is the number of bits, and this value is used to perform the shift. + + .. rust-example:: + + fn main() { + let bits: u32 = 61; + let shifts = vec![4, 40]; + + for sh in shifts { + let (result, overflowed) = bits.overflowing_shl(sh); + if overflowed { + println!("{bits} << {sh} shift too large"); + } else { + println!("{bits} << {sh} = {result}"); + } + } + } + + .. compliant_example:: + :id: compl_ex_xpPQqYeEPGIo + :status: draft + + This compliant example performs left shifts via the `checked_shl `_ + function and right shifts via the `checked_shr `_ function. + Both of these functions are defined in `core `_. + + ``::checked_shl(M)`` returns a value of type ``Option``: + + * If ``M < 0``\ , the output is ``None`` + * If ``0 <= M < N`` for ``T`` of size ``N`` bits, then the output is ``Some(T)`` + * If ``N <= M``\ , the output is ``None`` + + Checked shift operations make programmer intent explicit and eliminates out-of-range shifts. + Shifting by: + + * negative values is impossible because ``checked_shl`` only accepts unsigned integers as shift lengths, and + * greater than or equal to the number of bits that exist in the left operand returns a ``None`` value. + + .. rust-example:: + + fn main() { + let bits : u32 = 61; + // let shifts = vec![-1, 4, 40]; + // ^--- Compiler rejects negative shifts + let shifts = vec![4, 40]; + + for sh in shifts { + println!("{bits} << {sh} = {:?}", bits.checked_shl(sh)); + } + } diff --git a/src/coding-guidelines/expressions/gui_dCquvqE1csI3.rst.inc b/src/coding-guidelines/expressions/gui_dCquvqE1csI3.rst.inc new file mode 100644 index 00000000..39141feb --- /dev/null +++ b/src/coding-guidelines/expressions/gui_dCquvqE1csI3.rst.inc @@ -0,0 +1,296 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Ensure that integer operations do not result in arithmetic overflow + :id: gui_dCquvqE1csI3 + :category: required + :status: draft + :release: 1.0 - latest + :fls: fls_oFIRXBPXu6Zv + :decidability: decidable + :scope: system + :tags: security, performance, numerics + + Eliminate `arithmetic overflow `__ of both signed and unsigned integer types. + Any wraparound behavior must be explicitly specified to ensure the same behavior in both debug and release modes. + + This rule applies to the following primitive types: + + * ``i8`` + * ``i16`` + * ``i32`` + * ``i64`` + * ``i128`` + * ``u8`` + * ``u16`` + * ``u32`` + * ``u64`` + * ``u128`` + * ``usize`` + * ``isize`` + + .. rationale:: + :id: rat_LvrS1jTCXEOk + :status: draft + + Eliminate arithmetic overflow to avoid runtime panics and unexpected wraparound behavior. + Arithmetic overflow will panic in debug mode, but wraparound in release mode, resulting in inconsistent behavior. + Use explicit `wrapping `_ or + `saturating `_ semantics where these behaviors are intentional. + Range checking can be used to eliminate the possibility of arithmetic overflow. + + .. non_compliant_example:: + :id: non_compl_ex_cCh2RQUXeH0N + :status: draft + + This noncompliant code example can result in arithmetic overflow during the addition of the signed operands ``si_a`` and ``si_b``: + + .. rust-example:: + + fn add(si_a: i32, si_b: i32) { + let _sum: i32 = si_a + si_b; + // ... + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_BgUHiRB4kc4b_1 + :status: draft + + This compliant solution ensures that the addition operation cannot result in arithmetic overflow, + based on the maximum range of a signed 32-bit integer. + Functions such as + `overflowing_add `_, + `overflowing_sub `_, and + `overflowing_mul `_ + can also be used to detect overflow. + Code that invoked these functions would typically further restrict the range of possible values, + based on the anticipated range of the inputs. + + .. rust-example:: + + # #[derive(Debug)] + # enum ArithmeticError { Overflow, DivisionByZero } + use std::i32::{MAX as INT_MAX, MIN as INT_MIN}; + + fn add(si_a: i32, si_b: i32) -> Result { + if (si_b > 0 && si_a > INT_MAX - si_b) + || (si_b < 0 && si_a < INT_MIN - si_b) + { + Err(ArithmeticError::Overflow) + } else { + Ok(si_a + si_b) + } + } + + fn sub(si_a: i32, si_b: i32) -> Result { + if (si_b < 0 && si_a > INT_MAX + si_b) + || (si_b > 0 && si_a < INT_MIN + si_b) + { + Err(ArithmeticError::Overflow) + } else { + Ok(si_a - si_b) + } + } + + fn mul(si_a: i32, si_b: i32) -> Result { + if si_a == 0 || si_b == 0 { + return Ok(0); + } + + // Detect overflow before performing multiplication + if (si_a == -1 && si_b == INT_MIN) || (si_b == -1 && si_a == INT_MIN) { + Err(ArithmeticError::Overflow) + } else if (si_a > 0 && (si_b > INT_MAX / si_a || si_b < INT_MIN / si_a)) + || (si_a < 0 && (si_b > INT_MIN / si_a || si_b < INT_MAX / si_a)) + { + Err(ArithmeticError::Overflow) + } else { + Ok(si_a * si_b) + } + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_BgUHiRB4kc4c + :status: draft + + This compliant example uses safe checked addition instead of manual bounds checks. + Checked functions can reduce readability when complex arithmetic expressions are needed. + + .. rust-example:: + + # #[derive(Debug)] + # enum ArithmeticError { Overflow, DivisionByZero } + fn add(si_a: i32, si_b: i32) -> Result { + si_a.checked_add(si_b).ok_or(ArithmeticError::Overflow) + } + + fn sub(a: i32, b: i32) -> Result { + a.checked_sub(b).ok_or(ArithmeticError::Overflow) + } + + fn mul(a: i32, b: i32) -> Result { + a.checked_mul(b).ok_or(ArithmeticError::Overflow) + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_BgUHiRB4kc4b + :status: draft + + Wrapping behavior must be explicitly requested. This compliant example uses wrapping functions. + + .. rust-example:: + + fn add(a: i32, b: i32) -> i32 { + a.wrapping_add(b) + } + + fn sub(a: i32, b: i32) -> i32 { + a.wrapping_sub(b) + } + + fn mul(a: i32, b: i32) -> i32 { + a.wrapping_mul(b) + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_BhUHiRB4kc4b + :status: draft + + Wrapping behavior call also be achieved using the ``Wrapping`` type as in this compliant solution. + The ``Wrapping`` type is a ``struct`` found in the ``std::num`` module that explicitly enables two's complement + wrapping arithmetic for the inner type ``T`` (which must be an integer or ``usize/isize``). + The ``Wrapping`` type provides a consistent way to force wrapping behavior in all build modes, + which is useful in specific scenarios like implementing cryptography or hash functions where wrapping arithmetic is the intended behavior. + + .. rust-example:: + + use std::num::Wrapping; + + fn add(si_a: Wrapping, si_b: Wrapping) -> Wrapping { + si_a + si_b + } + + fn sub(si_a: Wrapping, si_b: Wrapping) -> Wrapping { + si_a - si_b + } + + fn mul(si_a: Wrapping, si_b: Wrapping) -> Wrapping { + si_a * si_b + } + + fn main() { + let si_a = Wrapping(i32::MAX); + let si_b = Wrapping(i32::MAX); + println!("{} + {} = {}", si_a, si_b, add(si_a, si_b)) + } + + .. compliant_example:: + :id: compl_ex_BgUHiSB4kc4b + :status: draft + + Saturation semantics means that instead of wrapping around or resulting in an error, + any result that falls outside the valid range of the integer type is clamped: + + - To the maximum value, if the result were to be greater than the maximum value, or + - To the minimum value, if the result were to be smaller than the minimum, + + Saturation semantics always conform to this rule because they ensure that integer operations do not result in arithmetic overflow. + This compliant solution shows how to use saturating functions to provide saturation semantics for some basic arithmetic operations. + + .. rust-example:: + + fn add(a: i32, b: i32) -> i32 { + a.saturating_add(b) + } + + fn sub(a: i32, b: i32) -> i32 { + a.saturating_sub(b) + } + + fn mul(a: i32, b: i32) -> i32 { + a.saturating_mul(b) + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_BgUHiSB4kd4b + :status: draft + + ``Saturating`` is a wrapper type in Rust's ``core`` library (``core::num::Saturating``) that makes arithmetic operations on the wrapped value perform saturating arithmetic instead of wrapping, panicking, or overflowing. + ``Saturating`` is useful when you have a section of code or a data type where all arithmetic must be saturating. + This compliant solution uses the ``Saturating`` type to define several functions that perform basic integer operations using saturation semantics. + + .. rust-example:: + + use std::num::Saturating; + + fn add(si_a: Saturating, si_b: Saturating) -> Saturating { + si_a + si_b + } + + fn sub(si_a: Saturating, si_b: Saturating) -> Saturating { + si_a - si_b + } + + fn mul(si_a: Saturating, si_b: Saturating) -> Saturating { + si_a * si_b + } + + fn main() { + let si_a = Saturating(i32::MAX); + let si_b = Saturating(i32::MAX); + println!("{} + {} = {}", si_a, si_b, add(si_a, si_b)) + } + + .. non_compliant_example:: + :id: non_compl_ex_cCh2RQUXeH0O + :status: draft + + This noncompliant code example example prevents divide-by-zero errors, but does not prevent arithmetic overflow. + + .. rust-example:: + + # #[derive(Debug)] + # enum DivError { DivisionByZero, Overflow } + fn div(s_a: i64, s_b: i64) -> Result { + if s_b == 0 { + Err(DivError::DivisionByZero) + } else { + Ok(s_a / s_b) + } + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_BgUHiRB4kc4d + :status: draft + + This compliant solution eliminates the possibility of both divide-by-zero errors and arithmetic overflow: + + .. rust-example:: + + # #[derive(Debug)] + # enum DivError { DivisionByZero, Overflow } + fn div(s_a: i64, s_b: i64) -> Result { + if s_b == 0 { + Err(DivError::DivisionByZero) + } else if s_a == i64::MIN && s_b == -1 { + Err(DivError::Overflow) + } else { + Ok(s_a / s_b) + } + } + # + # fn main() {} diff --git a/src/coding-guidelines/expressions/gui_iv9yCMHRgpE0.rst.inc b/src/coding-guidelines/expressions/gui_iv9yCMHRgpE0.rst.inc new file mode 100644 index 00000000..6d3af508 --- /dev/null +++ b/src/coding-guidelines/expressions/gui_iv9yCMHRgpE0.rst.inc @@ -0,0 +1,69 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: An integer shall not be converted to an invalid pointer + :id: gui_iv9yCMHRgpE0 + :category: + :status: draft + :release: + :fls: fls_9wgldua1u8yt + :decidability: undecidable + :scope: system + :tags: defect, undefined-behavior + + An expression of numeric type shall not be converted to a pointer if the resulting pointer + is incorrectly aligned, does not point to an entity of the referenced type, or is an invalid representation. + + .. rationale:: + :id: rat_OhxKm751axKw + :status: draft + + The mapping between pointers and integers must be consistent with the addressing structure of the + execution environment. Issues may arise, for example, on architectures that have a segmented memory model. + + .. non_compliant_example:: + :id: non_compl_ex_CkytKjRQezfQ + :status: draft + + This example makes assumptions about the layout of the address space that do not hold on all platforms. + The manipulated address may have discarded part of the original address space, and the flag may + silently interfere with the address value. On platforms where pointers are 64-bits this may have + particularly unexpected results. + + .. rust-example:: + + fn f1(flag: u32, ptr: * const u32) { + /* ... */ + let mut rep = ptr as usize; + rep = (rep & 0x7fffff) | ((flag as usize) << 23); + let _p2 = rep as * const u32; + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_oBoluiKSvREu + :status: draft + + This compliant solution uses a struct to provide storage for both the pointer and the flag value. + This solution is portable to machines of different word sizes, both smaller and larger than 32 bits, + working even when pointers cannot be represented in any integer type. + + .. rust-example:: + + struct PtrFlag { + pointer: * const u32, + flag: u32 + } + + fn f2(flag: u32, ptr: * const u32) { + let _ptrflag = PtrFlag { + pointer: ptr, + flag: flag + }; + /* ... */ + } + # + # fn main() {} diff --git a/src/coding-guidelines/expressions/gui_kMbiWbn8Z6g5.rst.inc b/src/coding-guidelines/expressions/gui_kMbiWbn8Z6g5.rst.inc new file mode 100644 index 00000000..d2952bdf --- /dev/null +++ b/src/coding-guidelines/expressions/gui_kMbiWbn8Z6g5.rst.inc @@ -0,0 +1,93 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Do not divide by 0 + :id: gui_kMbiWbn8Z6g5 + :category: required + :status: draft + :release: latest + :fls: fls_Q9dhNiICGIfr + :decidability: undecidable + :scope: system + :tags: numerics, defect + + Integer division by zero results in a panic. + This includes both `division expressions + `_ and `remainder expressions + `_. + + Division and remainder expressions on signed integers are also susceptible to arithmetic overflow. + Overflow is covered in full by the guideline `Ensure that integer operations do not result in arithmetic overflow`. + + This rule applies to the following primitive integer types: + + * ``i8`` + * ``i16`` + * ``i32`` + * ``i64`` + * ``i128`` + * ``u8`` + * ``u16`` + * ``u32`` + * ``u64`` + * ``u128`` + * ``usize`` + * ``isize`` + + This rule does not apply to evaluation of the :std:`core::ops::Div` trait on types other than `integer + types `_. + + This rule is a less strict version of `Do not use an integer type as a divisor during integer division`. + All code that complies with that rule also complies with this rule. + + .. rationale:: + :id: rat_h84NjY2tLSBW + :status: draft + + Integer division by zero results in a panic; an abnormal program state that may terminate the process and must be avoided. + + .. non_compliant_example:: + :id: non_compl_ex_LLs3vY8aGz0F + :status: draft + + This non-compliant example panics when the right operand is zero for either the division or remainder operations. + + .. rust-example:: + :compile_fail: + + fn main() { + let x = 0; + let _y = 5 / x; // Results in a panic. + let _z = 5 % x; // Also results in a panic. + } + + .. compliant_example:: + :id: compl_ex_Ri9pP5Ch3kcc + :status: draft + + Compliant examples from `Do not use an integer type as a divisor during integer division` are also valid for this rule. + Additionally, the check for zero can be performed manually, as in this compliant example. + However, as the complexity of the control flow leading to the invariant increases, + it becomes increasingly harder for both programmers and static analysis tools to reason about it. + + Note that the test for arithmetic overflow is not necessary for unsigned integers. + + .. rust-example:: + + fn main() { + // Checking for zero by hand + let x = 0u32; + let _y = if x != 0u32 { + 5u32 / x + } else { + 0u32 + }; + + let _z = if x != 0u32 { + 5u32 % x + } else { + 0u32 + }; + } diff --git a/src/coding-guidelines/expressions/index.rst b/src/coding-guidelines/expressions/index.rst new file mode 100644 index 00000000..3487e4c6 --- /dev/null +++ b/src/coding-guidelines/expressions/index.rst @@ -0,0 +1,17 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +Expressions +=========== + +.. include:: gui_7y0GAMmtMhch.rst.inc +.. include:: gui_ADHABsmK9FXz.rst.inc +.. include:: gui_HDnAZ7EZ4z6G.rst.inc +.. include:: gui_LvmzGKdsAgI5.rst.inc +.. include:: gui_PM8Vpf7lZ51U.rst.inc +.. include:: gui_RHvQj8BHlz9b.rst.inc +.. include:: gui_dCquvqE1csI3.rst.inc +.. include:: gui_iv9yCMHRgpE0.rst.inc +.. include:: gui_kMbiWbn8Z6g5.rst.inc diff --git a/src/coding-guidelines/ffi.rst b/src/coding-guidelines/ffi/index.rst similarity index 97% rename from src/coding-guidelines/ffi.rst rename to src/coding-guidelines/ffi/index.rst index fc48d03b..dba1887e 100644 --- a/src/coding-guidelines/ffi.rst +++ b/src/coding-guidelines/ffi/index.rst @@ -3,6 +3,6 @@ .. default-domain:: coding-guidelines -FFI +Ffi === diff --git a/src/coding-guidelines/functions.rst b/src/coding-guidelines/functions/index.rst similarity index 100% rename from src/coding-guidelines/functions.rst rename to src/coding-guidelines/functions/index.rst diff --git a/src/coding-guidelines/generics.rst b/src/coding-guidelines/generics/index.rst similarity index 100% rename from src/coding-guidelines/generics.rst rename to src/coding-guidelines/generics/index.rst diff --git a/src/coding-guidelines/implementations.rst b/src/coding-guidelines/implementations/index.rst similarity index 99% rename from src/coding-guidelines/implementations.rst rename to src/coding-guidelines/implementations/index.rst index 2c959338..14a2e0b9 100644 --- a/src/coding-guidelines/implementations.rst +++ b/src/coding-guidelines/implementations/index.rst @@ -5,3 +5,4 @@ Implementations =============== + diff --git a/src/coding-guidelines/index.rst b/src/coding-guidelines/index.rst index 608b8786..c0081ca2 100644 --- a/src/coding-guidelines/index.rst +++ b/src/coding-guidelines/index.rst @@ -9,22 +9,22 @@ Coding Guidelines .. toctree:: :maxdepth: 1 - types-and-traits - patterns - expressions - values - statements - functions - associated-items - implementations - generics - attributes - entities-and-resolution - ownership-and-destruction - exceptions-and-errors - concurrency - program-structure-and-compilation - unsafety - macros - ffi - inline-assembly + types-and-traits/index + patterns/index + expressions/index + values/index + statements/index + functions/index + associated-items/index + implementations/index + generics/index + attributes/index + entities-and-resolution/index + ownership-and-destruction/index + exceptions-and-errors/index + concurrency/index + program-structure-and-compilation/index + unsafety/index + macros/index + ffi/index + inline-assembly/index diff --git a/src/coding-guidelines/inline-assembly.rst b/src/coding-guidelines/inline-assembly/index.rst similarity index 99% rename from src/coding-guidelines/inline-assembly.rst rename to src/coding-guidelines/inline-assembly/index.rst index 3885903d..dcee521a 100644 --- a/src/coding-guidelines/inline-assembly.rst +++ b/src/coding-guidelines/inline-assembly/index.rst @@ -5,3 +5,4 @@ Inline Assembly =============== + diff --git a/src/coding-guidelines/macros.rst b/src/coding-guidelines/macros.rst deleted file mode 100644 index 7b5e2b66..00000000 --- a/src/coding-guidelines/macros.rst +++ /dev/null @@ -1,539 +0,0 @@ -.. SPDX-License-Identifier: MIT OR Apache-2.0 - SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors - -.. default-domain:: coding-guidelines - -Macros -====== - -.. guideline:: Shall not use Declarative Macros - :id: gui_h0uG1C9ZjryA - :category: mandatory - :status: draft - :release: todo - :fls: fls_xa7lp0zg1ol2 - :decidability: decidable - :scope: system - :tags: reduce-human-error - - Description of the guideline goes here. - - .. rationale:: - :id: rat_U3AEUPyaUhcb - :status: draft - - Explanation of why this guideline is important. - - .. non_compliant_example:: - :id: non_compl_ex_Gb4zimei8cNI - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Non-compliant implementation - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_Pw7YCh4Iv47Z - :status: draft - - Explanation of code example - - .. rust-example:: - - fn example_function() { - // Compliant implementation - } - # - # fn main() {} - -.. guideline:: Procedural macros should not be used - :id: gui_66FSqzD55VRZ - :category: advisory - :status: draft - :release: 1.85.0;1.85.1 - :fls: fls_wn1i6hzg2ff7 - :decidability: decidable - :scope: crate - :tags: readability, reduce-human-error - - Macros should be expressed using declarative syntax - in preference to procedural syntax. - - .. rationale:: - :id: rat_AmCavSymv3Ev - :status: draft - - Procedural macros are not restricted to pure transcription and can contain arbitrary Rust code. - This means they can be harder to understand, and cannot be as easily proved to work as intended. - Procedural macros can have arbitrary side effects, which can exhaust compiler resources or - expose a vulnerability for users of adopted code. - - .. non_compliant_example:: - :id: non_compl_ex_pJhVZW6a1HP9 - :status: draft - - (example of a simple expansion using a proc-macro) - - .. rust-example:: - - // TODO - fn main() {} - - .. compliant_example:: - :id: compl_ex_4VFyucETB7C3 - :status: draft - - (example of the same simple expansion using a declarative macro) - - .. rust-example:: - - // TODO - fn main() {} - -.. guideline:: A macro should not be used in place of a function - :id: gui_2jjWUoF1teOY - :category: mandatory - :status: draft - :release: todo - :fls: fls_xa7lp0zg1ol2 - :decidability: decidable - :scope: system - :tags: reduce-human-error - - Functions should always be preferred over macros, except when macros provide essential functionality that functions cannot, such as variadic interfaces, compile-time code generation, or syntax extensions via custom derive and attribute macros. - - | - - .. rationale:: - :id: rat_M9bp23ctkzQ7 - :status: draft - - Although the compiler reports both the macro expansion and its invocation site, diagnostics originating within macros can be more difficult to interpret than those from ordinary function or type definitions. Complex or deeply nested macros may obscure intent and hinder static analysis, increasing the risk of misinterpretation or overlooked errors during code review. - - - **Debugging Complexity** - - - Errors point to expanded code rather than source locations, making it difficult to trace compile-time errors back to the original macro invocation. - - **Optimization** - - - Macros may inhibit compiler optimizations that work better with functions. - - Macros act like ``#[inline(always)]`` functions, which can lead to code bloat. - - They don't benefit from the compiler's inlining heuristics, missing out on selective inlining where the compiler decides when inlining is beneficial. - - **Functions provide** - - - Clear type signatures. - - Predictable behavior. - - Proper stack traces. - - Consistent optimization opportunities. - - - .. non_compliant_example:: - :id: non_compl_ex_TZgk2vG42t2r - :status: draft - - Using a macro where a simple function would suffice, leads to hidden mutation: - - .. rust-example:: - - macro_rules! increment_and_double { - ($x:expr) => { - { - $x += 1; // mutation is implicit - $x * 2 - } - }; - } - - fn main() { - let mut num = 5; - let result = increment_and_double!(num); - println!("Result: {}, Num: {}", result, num); - // Result: 12, Num: 6 - } - - In this example, calling the macro both increments and returns the value in one goβ€”without any clear indication in its "signature" that it mutates its argument. As a result, num is changed behind the scenes, which can surprise readers and make debugging more difficult. - - - .. compliant_example:: - :id: compl_ex_iPTgzrvO7qr3 - :status: draft - - The same functionality, implemented as a function with explicit borrowing: - - .. rust-example:: - - fn increment_and_double(x: &mut i32) -> i32 { - *x += 1; // mutation is explicit - *x * 2 - } - - fn main() { - let mut num = 5; - let result = increment_and_double(&mut num); - println!("Result: {}, Num: {}", result, num); - // Result: 12, Num: 6 - } - - The function version makes the mutation and borrowing explicit in its signature, improving readability, safety, and debuggability. - - - -.. guideline:: Shall not use Function-like Macros - :id: gui_WJlWqgIxmE8P - :category: mandatory - :status: draft - :release: todo - :fls: fls_utd3zqczix - :decidability: decidable - :scope: system - :tags: reduce-human-error - - Description of the guideline goes here. - - .. rationale:: - :id: rat_C8RRidiVzhRj - :status: draft - - Explanation of why this guideline is important. - - .. non_compliant_example:: - :id: non_compl_ex_TjRiRkmBY6wG - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Non-compliant implementation - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_AEKEOYhBWPMl - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Compliant implementation - } - # - # fn main() {} - -.. guideline:: Shall not invoke macros - :id: gui_a1mHfjgKk4Xr - :category: mandatory - :status: draft - :release: todo - :fls: fls_vnvt40pa48n8 - :decidability: decidable - :scope: system - :tags: reduce-human-error - - Description of the guideline goes here. - - .. rationale:: - :id: rat_62mSorNF05kD - :status: draft - - Explanation of why this guideline is important. - - .. non_compliant_example:: - :id: non_compl_ex_hP5KLhqQfDcd - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Non-compliant implementation - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_ti7GWHCOhUvT - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Compliant implementation - } - # - # fn main() {} - -.. guideline:: Shall not write code that expands macros - :id: gui_uuDOArzyO3Qw - :category: mandatory - :status: draft - :release: todo - :fls: fls_wjldgtio5o75 - :decidability: decidable - :scope: system - :tags: reduce-human-error - - Description of the guideline goes here. - - .. rationale:: - :id: rat_dNgSvC0SZ3JJ - :status: draft - - Explanation of why this guideline is important. - - .. non_compliant_example:: - :id: non_compl_ex_g9j8shyGM2Rh - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Non-compliant implementation - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_cFPg6y7upNdl - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Compliant implementation - } - # - # fn main() {} - -.. guideline:: Shall ensure complete hygiene of macros - :id: gui_8hs33nyp0ipX - :category: mandatory - :status: draft - :release: todo - :fls: fls_xlfo7di0gsqz - :decidability: decidable - :scope: system - :tags: reduce-human-error - - Description of the guideline goes here. - - .. rationale:: - :id: rat_e9iS187skbHH - :status: draft - - Explanation of why this guideline is important. - - .. non_compliant_example:: - :id: non_compl_ex_lRt4LBen6Lkc - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Non-compliant implementation - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_GLP05s9c1g8N - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Compliant implementation - } - # - # fn main() {} - -.. guideline:: Attribute macros shall not be used - :id: gui_13XWp3mb0g2P - :category: required - :status: draft - :release: todo - :fls: fls_4vjbkm4ceymk - :decidability: decidable - :scope: system - :tags: reduce-human-error - - Attribute macros shall neither be declared nor invoked. - Prefer less powerful macros that only extend source code. - - .. rationale:: - :id: rat_X8uCF5yx7Mpo - :status: draft - - Attribute macros are able to rewrite items entirely or in other unexpected ways which can cause confusion and introduce errors. - - .. non_compliant_example:: - :id: non_compl_ex_eW374waRPbeL - :status: draft - - The ``#[test]`` attribute macro transforms the function into a test harness entry point. - - .. rust-example:: - :no_run: - - #[test] // non-compliant: attribute macro rewrites the item - fn example_test() { - assert!(true); - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_Mg8ePOgbGJeW - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Compliant implementation - } - # - # fn main() {} - -.. guideline:: Do not hide unsafe blocks within macro expansions - :id: gui_FRLaMIMb4t3S - :category: required - :status: draft - :release: todo - :fls: fls_4vjbkm4ceymk - :decidability: todo - :scope: todo - :tags: reduce-human-error - - Description of the guideline goes here. - - .. rationale:: - :id: rat_WJubG7KuUDLW - :status: draft - - Explanation of why this guideline is important. - - .. non_compliant_example:: - :id: non_compl_ex_AyFnP0lJLHxi - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Non-compliant implementation - } - # - # fn main() {} - - .. compliant_example:: - :id: compl_ex_pO5gP1aj2v4F - :status: draft - - Explanation of code example. - - .. rust-example:: - - fn example_function() { - // Compliant implementation - } - # - # fn main() {} - -.. guideline:: Names in a macro definition shall use a fully qualified path - :id: gui_SJMrWDYZ0dN4 - :category: required - :status: draft - :release: 1.85.0;1.85.1 - :fls: fls_7kb6ltajgiou - :decidability: decidable - :scope: module - :tags: reduce-human-error - - Each name inside of the definition of a macro shall either use a global path or path prefixed with $crate. - - .. rationale:: - :id: rat_VRNXaxmW1l2s - :status: draft - - Using a path that refers to an entity relatively inside of a macro subjects it to path resolution - results which may change depending on where the macro is used. The intended path to refer to an entity - can be shadowed when using a macro leading to unexpected behaviors. This could lead to developer confusion - about why a macro behaves differently in diffenent locations, or confusion about where entity in a macro - will resolve to. - - .. non_compliant_example:: - :id: non_compl_ex_m2XR1ihTbCQS - :status: draft - - The following is a macro which shows referring to a vector entity using a non-global path. Depending on - where the macro is used a different `Vec` could be used than is intended. If scope where this is used - defines a struct `Vec` which is not preset at the macro definition, the macro user might be intending to - use that in the macro. - - .. rust-example:: - - fn main() { - #[macro_export] - macro_rules! vec { - ( $( $x:expr ),* ) => { - { - let mut temp_vec = Vec::new(); // non-global path - $( - temp_vec.push($x); - )* - temp_vec - } - }; - } - } - - .. compliant_example:: - :id: compl_ex_xyaShvxL9JAM - :status: draft - - The following is a macro refers to Vec using a global path. Even if there is a different struct called - `Vec` defined in the scope of the macro usage, this macro will unambiguously use the `Vec` from the - Standard Library. - - .. rust-example:: - - fn main() { - #[macro_export] - macro_rules! vec { - ( $( $x:expr ),* ) => { - { - let mut temp_vec = ::std::vec::Vec::new(); // global path - $( - temp_vec.push($x); - )* - temp_vec - } - }; - } - } diff --git a/src/coding-guidelines/macros/gui_13XWp3mb0g2P.rst.inc b/src/coding-guidelines/macros/gui_13XWp3mb0g2P.rst.inc new file mode 100644 index 00000000..9a588e25 --- /dev/null +++ b/src/coding-guidelines/macros/gui_13XWp3mb0g2P.rst.inc @@ -0,0 +1,53 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Attribute macros shall not be used + :id: gui_13XWp3mb0g2P + :category: required + :status: draft + :release: todo + :fls: fls_4vjbkm4ceymk + :decidability: decidable + :scope: system + :tags: reduce-human-error + + Attribute macros shall neither be declared nor invoked. + Prefer less powerful macros that only extend source code. + + .. rationale:: + :id: rat_X8uCF5yx7Mpo + :status: draft + + Attribute macros are able to rewrite items entirely or in other unexpected ways which can cause confusion and introduce errors. + + .. non_compliant_example:: + :id: non_compl_ex_eW374waRPbeL + :status: draft + + The ``#[test]`` attribute macro transforms the function into a test harness entry point. + + .. rust-example:: + :no_run: + + #[test] // non-compliant: attribute macro rewrites the item + fn example_test() { + assert!(true); + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_Mg8ePOgbGJeW + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Compliant implementation + } + # + # fn main() {} diff --git a/src/coding-guidelines/macros/gui_2jjWUoF1teOY.rst.inc b/src/coding-guidelines/macros/gui_2jjWUoF1teOY.rst.inc new file mode 100644 index 00000000..68ed1039 --- /dev/null +++ b/src/coding-guidelines/macros/gui_2jjWUoF1teOY.rst.inc @@ -0,0 +1,92 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: A macro should not be used in place of a function + :id: gui_2jjWUoF1teOY + :category: mandatory + :status: draft + :release: todo + :fls: fls_xa7lp0zg1ol2 + :decidability: decidable + :scope: system + :tags: reduce-human-error + + Functions should always be preferred over macros, except when macros provide essential functionality that functions cannot, such as variadic interfaces, compile-time code generation, or syntax extensions via custom derive and attribute macros. + + | + + .. rationale:: + :id: rat_M9bp23ctkzQ7 + :status: draft + + Although the compiler reports both the macro expansion and its invocation site, diagnostics originating within macros can be more difficult to interpret than those from ordinary function or type definitions. Complex or deeply nested macros may obscure intent and hinder static analysis, increasing the risk of misinterpretation or overlooked errors during code review. + + + **Debugging Complexity** + + - Errors point to expanded code rather than source locations, making it difficult to trace compile-time errors back to the original macro invocation. + + **Optimization** + + - Macros may inhibit compiler optimizations that work better with functions. + - Macros act like ``#[inline(always)]`` functions, which can lead to code bloat. + - They don't benefit from the compiler's inlining heuristics, missing out on selective inlining where the compiler decides when inlining is beneficial. + + **Functions provide** + + - Clear type signatures. + - Predictable behavior. + - Proper stack traces. + - Consistent optimization opportunities. + + + .. non_compliant_example:: + :id: non_compl_ex_TZgk2vG42t2r + :status: draft + + Using a macro where a simple function would suffice, leads to hidden mutation: + + .. rust-example:: + + macro_rules! increment_and_double { + ($x:expr) => { + { + $x += 1; // mutation is implicit + $x * 2 + } + }; + } + + fn main() { + let mut num = 5; + let result = increment_and_double!(num); + println!("Result: {}, Num: {}", result, num); + // Result: 12, Num: 6 + } + + In this example, calling the macro both increments and returns the value in one goβ€”without any clear indication in its "signature" that it mutates its argument. As a result, num is changed behind the scenes, which can surprise readers and make debugging more difficult. + + + .. compliant_example:: + :id: compl_ex_iPTgzrvO7qr3 + :status: draft + + The same functionality, implemented as a function with explicit borrowing: + + .. rust-example:: + + fn increment_and_double(x: &mut i32) -> i32 { + *x += 1; // mutation is explicit + *x * 2 + } + + fn main() { + let mut num = 5; + let result = increment_and_double(&mut num); + println!("Result: {}, Num: {}", result, num); + // Result: 12, Num: 6 + } + + The function version makes the mutation and borrowing explicit in its signature, improving readability, safety, and debuggability. diff --git a/src/coding-guidelines/macros/gui_66FSqzD55VRZ.rst.inc b/src/coding-guidelines/macros/gui_66FSqzD55VRZ.rst.inc new file mode 100644 index 00000000..5b4a3611 --- /dev/null +++ b/src/coding-guidelines/macros/gui_66FSqzD55VRZ.rst.inc @@ -0,0 +1,48 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Procedural macros should not be used + :id: gui_66FSqzD55VRZ + :category: advisory + :status: draft + :release: 1.85.0;1.85.1 + :fls: fls_wn1i6hzg2ff7 + :decidability: decidable + :scope: crate + :tags: readability, reduce-human-error + + Macros should be expressed using declarative syntax + in preference to procedural syntax. + + .. rationale:: + :id: rat_AmCavSymv3Ev + :status: draft + + Procedural macros are not restricted to pure transcription and can contain arbitrary Rust code. + This means they can be harder to understand, and cannot be as easily proved to work as intended. + Procedural macros can have arbitrary side effects, which can exhaust compiler resources or + expose a vulnerability for users of adopted code. + + .. non_compliant_example:: + :id: non_compl_ex_pJhVZW6a1HP9 + :status: draft + + (example of a simple expansion using a proc-macro) + + .. rust-example:: + + // TODO + fn main() {} + + .. compliant_example:: + :id: compl_ex_4VFyucETB7C3 + :status: draft + + (example of the same simple expansion using a declarative macro) + + .. rust-example:: + + // TODO + fn main() {} diff --git a/src/coding-guidelines/macros/gui_8hs33nyp0ipX.rst.inc b/src/coding-guidelines/macros/gui_8hs33nyp0ipX.rst.inc new file mode 100644 index 00000000..e4fb6923 --- /dev/null +++ b/src/coding-guidelines/macros/gui_8hs33nyp0ipX.rst.inc @@ -0,0 +1,50 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Shall ensure complete hygiene of macros + :id: gui_8hs33nyp0ipX + :category: mandatory + :status: draft + :release: todo + :fls: fls_xlfo7di0gsqz + :decidability: decidable + :scope: system + :tags: reduce-human-error + + Description of the guideline goes here. + + .. rationale:: + :id: rat_e9iS187skbHH + :status: draft + + Explanation of why this guideline is important. + + .. non_compliant_example:: + :id: non_compl_ex_lRt4LBen6Lkc + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Non-compliant implementation + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_GLP05s9c1g8N + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Compliant implementation + } + # + # fn main() {} diff --git a/src/coding-guidelines/macros/gui_FRLaMIMb4t3S.rst.inc b/src/coding-guidelines/macros/gui_FRLaMIMb4t3S.rst.inc new file mode 100644 index 00000000..9edf655f --- /dev/null +++ b/src/coding-guidelines/macros/gui_FRLaMIMb4t3S.rst.inc @@ -0,0 +1,50 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Do not hide unsafe blocks within macro expansions + :id: gui_FRLaMIMb4t3S + :category: required + :status: draft + :release: todo + :fls: fls_4vjbkm4ceymk + :decidability: todo + :scope: todo + :tags: reduce-human-error + + Description of the guideline goes here. + + .. rationale:: + :id: rat_WJubG7KuUDLW + :status: draft + + Explanation of why this guideline is important. + + .. non_compliant_example:: + :id: non_compl_ex_AyFnP0lJLHxi + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Non-compliant implementation + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_pO5gP1aj2v4F + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Compliant implementation + } + # + # fn main() {} diff --git a/src/coding-guidelines/macros/gui_SJMrWDYZ0dN4.rst.inc b/src/coding-guidelines/macros/gui_SJMrWDYZ0dN4.rst.inc new file mode 100644 index 00000000..550901a7 --- /dev/null +++ b/src/coding-guidelines/macros/gui_SJMrWDYZ0dN4.rst.inc @@ -0,0 +1,77 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Names in a macro definition shall use a fully qualified path + :id: gui_SJMrWDYZ0dN4 + :category: required + :status: draft + :release: 1.85.0;1.85.1 + :fls: fls_7kb6ltajgiou + :decidability: decidable + :scope: module + :tags: reduce-human-error + + Each name inside of the definition of a macro shall either use a global path or path prefixed with $crate. + + .. rationale:: + :id: rat_VRNXaxmW1l2s + :status: draft + + Using a path that refers to an entity relatively inside of a macro subjects it to path resolution + results which may change depending on where the macro is used. The intended path to refer to an entity + can be shadowed when using a macro leading to unexpected behaviors. This could lead to developer confusion + about why a macro behaves differently in diffenent locations, or confusion about where entity in a macro + will resolve to. + + .. non_compliant_example:: + :id: non_compl_ex_m2XR1ihTbCQS + :status: draft + + The following is a macro which shows referring to a vector entity using a non-global path. Depending on + where the macro is used a different `Vec` could be used than is intended. If scope where this is used + defines a struct `Vec` which is not preset at the macro definition, the macro user might be intending to + use that in the macro. + + .. rust-example:: + + fn main() { + #[macro_export] + macro_rules! vec { + ( $( $x:expr ),* ) => { + { + let mut temp_vec = Vec::new(); // non-global path + $( + temp_vec.push($x); + )* + temp_vec + } + }; + } + } + + .. compliant_example:: + :id: compl_ex_xyaShvxL9JAM + :status: draft + + The following is a macro refers to Vec using a global path. Even if there is a different struct called + `Vec` defined in the scope of the macro usage, this macro will unambiguously use the `Vec` from the + Standard Library. + + .. rust-example:: + + fn main() { + #[macro_export] + macro_rules! vec { + ( $( $x:expr ),* ) => { + { + let mut temp_vec = ::std::vec::Vec::new(); // global path + $( + temp_vec.push($x); + )* + temp_vec + } + }; + } + } diff --git a/src/coding-guidelines/macros/gui_WJlWqgIxmE8P.rst.inc b/src/coding-guidelines/macros/gui_WJlWqgIxmE8P.rst.inc new file mode 100644 index 00000000..6d3b2351 --- /dev/null +++ b/src/coding-guidelines/macros/gui_WJlWqgIxmE8P.rst.inc @@ -0,0 +1,50 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Shall not use Function-like Macros + :id: gui_WJlWqgIxmE8P + :category: mandatory + :status: draft + :release: todo + :fls: fls_utd3zqczix + :decidability: decidable + :scope: system + :tags: reduce-human-error + + Description of the guideline goes here. + + .. rationale:: + :id: rat_C8RRidiVzhRj + :status: draft + + Explanation of why this guideline is important. + + .. non_compliant_example:: + :id: non_compl_ex_TjRiRkmBY6wG + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Non-compliant implementation + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_AEKEOYhBWPMl + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Compliant implementation + } + # + # fn main() {} diff --git a/src/coding-guidelines/macros/gui_a1mHfjgKk4Xr.rst.inc b/src/coding-guidelines/macros/gui_a1mHfjgKk4Xr.rst.inc new file mode 100644 index 00000000..1a25b8cf --- /dev/null +++ b/src/coding-guidelines/macros/gui_a1mHfjgKk4Xr.rst.inc @@ -0,0 +1,50 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Shall not invoke macros + :id: gui_a1mHfjgKk4Xr + :category: mandatory + :status: draft + :release: todo + :fls: fls_vnvt40pa48n8 + :decidability: decidable + :scope: system + :tags: reduce-human-error + + Description of the guideline goes here. + + .. rationale:: + :id: rat_62mSorNF05kD + :status: draft + + Explanation of why this guideline is important. + + .. non_compliant_example:: + :id: non_compl_ex_hP5KLhqQfDcd + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Non-compliant implementation + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_ti7GWHCOhUvT + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Compliant implementation + } + # + # fn main() {} diff --git a/src/coding-guidelines/macros/gui_h0uG1C9ZjryA.rst.inc b/src/coding-guidelines/macros/gui_h0uG1C9ZjryA.rst.inc new file mode 100644 index 00000000..101b6c1a --- /dev/null +++ b/src/coding-guidelines/macros/gui_h0uG1C9ZjryA.rst.inc @@ -0,0 +1,50 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Shall not use Declarative Macros + :id: gui_h0uG1C9ZjryA + :category: mandatory + :status: draft + :release: todo + :fls: fls_xa7lp0zg1ol2 + :decidability: decidable + :scope: system + :tags: reduce-human-error + + Description of the guideline goes here. + + .. rationale:: + :id: rat_U3AEUPyaUhcb + :status: draft + + Explanation of why this guideline is important. + + .. non_compliant_example:: + :id: non_compl_ex_Gb4zimei8cNI + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Non-compliant implementation + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_Pw7YCh4Iv47Z + :status: draft + + Explanation of code example + + .. rust-example:: + + fn example_function() { + // Compliant implementation + } + # + # fn main() {} diff --git a/src/coding-guidelines/macros/gui_uuDOArzyO3Qw.rst.inc b/src/coding-guidelines/macros/gui_uuDOArzyO3Qw.rst.inc new file mode 100644 index 00000000..442ddd5f --- /dev/null +++ b/src/coding-guidelines/macros/gui_uuDOArzyO3Qw.rst.inc @@ -0,0 +1,50 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +.. guideline:: Shall not write code that expands macros + :id: gui_uuDOArzyO3Qw + :category: mandatory + :status: draft + :release: todo + :fls: fls_wjldgtio5o75 + :decidability: decidable + :scope: system + :tags: reduce-human-error + + Description of the guideline goes here. + + .. rationale:: + :id: rat_dNgSvC0SZ3JJ + :status: draft + + Explanation of why this guideline is important. + + .. non_compliant_example:: + :id: non_compl_ex_g9j8shyGM2Rh + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Non-compliant implementation + } + # + # fn main() {} + + .. compliant_example:: + :id: compl_ex_cFPg6y7upNdl + :status: draft + + Explanation of code example. + + .. rust-example:: + + fn example_function() { + // Compliant implementation + } + # + # fn main() {} diff --git a/src/coding-guidelines/macros/index.rst b/src/coding-guidelines/macros/index.rst new file mode 100644 index 00000000..e81788fa --- /dev/null +++ b/src/coding-guidelines/macros/index.rst @@ -0,0 +1,18 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +Macros +====== + +.. include:: gui_13XWp3mb0g2P.rst.inc +.. include:: gui_2jjWUoF1teOY.rst.inc +.. include:: gui_66FSqzD55VRZ.rst.inc +.. include:: gui_8hs33nyp0ipX.rst.inc +.. include:: gui_FRLaMIMb4t3S.rst.inc +.. include:: gui_SJMrWDYZ0dN4.rst.inc +.. include:: gui_WJlWqgIxmE8P.rst.inc +.. include:: gui_a1mHfjgKk4Xr.rst.inc +.. include:: gui_h0uG1C9ZjryA.rst.inc +.. include:: gui_uuDOArzyO3Qw.rst.inc diff --git a/src/coding-guidelines/ownership-and-destruction.rst b/src/coding-guidelines/ownership-and-destruction/index.rst similarity index 87% rename from src/coding-guidelines/ownership-and-destruction.rst rename to src/coding-guidelines/ownership-and-destruction/index.rst index f69ce478..d6bdf421 100644 --- a/src/coding-guidelines/ownership-and-destruction.rst +++ b/src/coding-guidelines/ownership-and-destruction/index.rst @@ -3,5 +3,6 @@ .. default-domain:: coding-guidelines -Ownership and Destruction +Ownership And Destruction ========================= + diff --git a/src/coding-guidelines/patterns.rst b/src/coding-guidelines/patterns/index.rst similarity index 99% rename from src/coding-guidelines/patterns.rst rename to src/coding-guidelines/patterns/index.rst index 24775e88..de3185bd 100644 --- a/src/coding-guidelines/patterns.rst +++ b/src/coding-guidelines/patterns/index.rst @@ -5,3 +5,4 @@ Patterns ======== + diff --git a/src/coding-guidelines/program-structure-and-compilation.rst b/src/coding-guidelines/program-structure-and-compilation/index.rst similarity index 84% rename from src/coding-guidelines/program-structure-and-compilation.rst rename to src/coding-guidelines/program-structure-and-compilation/index.rst index b9b61cf7..047de02f 100644 --- a/src/coding-guidelines/program-structure-and-compilation.rst +++ b/src/coding-guidelines/program-structure-and-compilation/index.rst @@ -3,5 +3,6 @@ .. default-domain:: coding-guidelines -Program Structure and Compilation +Program Structure And Compilation ================================= + diff --git a/src/coding-guidelines/statements.rst b/src/coding-guidelines/statements/index.rst similarity index 99% rename from src/coding-guidelines/statements.rst rename to src/coding-guidelines/statements/index.rst index 83123877..17957b19 100644 --- a/src/coding-guidelines/statements.rst +++ b/src/coding-guidelines/statements/index.rst @@ -5,3 +5,4 @@ Statements ========== + diff --git a/src/coding-guidelines/types-and-traits.rst b/src/coding-guidelines/types-and-traits/gui_xztNdXA2oFNC.rst.inc similarity index 99% rename from src/coding-guidelines/types-and-traits.rst rename to src/coding-guidelines/types-and-traits/gui_xztNdXA2oFNC.rst.inc index 1c756f44..45573764 100644 --- a/src/coding-guidelines/types-and-traits.rst +++ b/src/coding-guidelines/types-and-traits/gui_xztNdXA2oFNC.rst.inc @@ -3,9 +3,6 @@ .. default-domain:: coding-guidelines -Types and Traits -================ - .. guideline:: Use strong types to differentiate between logically distinct values :id: gui_xztNdXA2oFNC :category: advisory diff --git a/src/coding-guidelines/types-and-traits/index.rst b/src/coding-guidelines/types-and-traits/index.rst new file mode 100644 index 00000000..c0c6d817 --- /dev/null +++ b/src/coding-guidelines/types-and-traits/index.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: MIT OR Apache-2.0 + SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +.. default-domain:: coding-guidelines + +Types and Traits +================ + +.. include:: gui_xztNdXA2oFNC.rst.inc diff --git a/src/coding-guidelines/unsafety.rst b/src/coding-guidelines/unsafety/index.rst similarity index 99% rename from src/coding-guidelines/unsafety.rst rename to src/coding-guidelines/unsafety/index.rst index e8e13198..7066759d 100644 --- a/src/coding-guidelines/unsafety.rst +++ b/src/coding-guidelines/unsafety/index.rst @@ -5,3 +5,4 @@ Unsafety ======== + diff --git a/src/coding-guidelines/values.rst b/src/coding-guidelines/values/index.rst similarity index 99% rename from src/coding-guidelines/values.rst rename to src/coding-guidelines/values/index.rst index 2fa02de0..c2419fc7 100644 --- a/src/coding-guidelines/values.rst +++ b/src/coding-guidelines/values/index.rst @@ -5,3 +5,4 @@ Values ====== +