Introduction

This is a (non-comprehensive) guide for JavaScript developers that are completely new to the C programming language. Some concepts and constructs translate fairly well between JavaScript and C, but which may be expressed differently, whereas others are a radical departure, like memory management. This guide provides a brief comparison and mapping of those constructs and concepts with concise examples.

The original authors1 of this guide were themselves JavaScript developers who were completely new to C. It is the guide the authors wish they had when they started on their C journey. That said, the authors would encourage you to read books and other material available on the Web to embrace C and its idioms rather than attempting to learn it exclusively through the lens of JavaScript. Meanwhile, this guide can help answers some question quickly, like: Does C support inheritance, threading, asynchronous programming, etc.? Assumptions:

  • Reader is a seasoned JavaScript developer.
  • Reader is completely new to C.

Goals:

  • Provide a brief comparison and mapping of various JavaScript topics to their counterparts in C.
  • Provide links to C reference, book and articles for further reading on topics.

Non-goals:

  • Discussion of design patterns and architectures.
  • Tutorial on the C language.
  • Reader is proficient in C after reading this guide.
  • While there are short examples that contrast JavaScript and C code for some topics, this guide is not meant to be a cookbook of coding recipes in the two languages.

1

The original authors of Microsoft's Rust for C#/.NET Developers were (in alphabetical order): Atif Aziz, Bastian Burger, Daniele Antonio Maggio, Dariusz Parys and Patrick Schuler.

The adaption work is done by @DevScholar on GitHub.

This book contains artificial intelligence generated content, and the content is audited.

License

MIT License

Copyright (c) Contributors.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

This book is adapted from Microsoft's Rust for C#/.NET Developers.

MIT License

Copyright (c) Microsoft Corporation. Portions Copyright (c) 2010 The Rust Project Developers

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Contributing

You are invited to contribute đź’– to this guide by opening issues and submitting pull requests!

Here are some ideas đź’ˇ for how and where you can help most with contributions:

  • Fix any spelling or grammatical mistakes you see as you read.

  • Fix technical inaccuracies.

  • Fix logical or compilation errors in code examples.

  • Improve the English, especially if it's your native tongue or you have excellent proficiency in the language.

  • Expand an explanation to provide more context or improve the clarity of some topic or concept.

  • Keep it fresh with changes in JavaScript and C. For example, if there is a change in JavaScript or C that brings the two languages closer together then some parts, including sample code, may need revision.

If you're making a small to modest correction, such fixing a spelling error or a syntax error in a code example, then feel free to submit a pull request directly. For changes that may require a large effort on your part (and reviewers as a result), it is strongly recommended that you submit an issue and seek approval of the maintainers/editors before investing your time. It will avoid heartbreak đź’” if the pull request is rejected for various reasons.

Making quick contributions has been made super simple. If you see an error on a page and happen to be online, you can click edit icon đź“ť in the corner of the page to edit the Markdown source of the content and submit a change.

Contribution Guidelines

  • Stick to the goals of this guide laid out in the introduction; put another way, avoid the non-goals!

  • Prefer to keep text short and use short, concise and realistic code examples to illustrate a point.

  • As much as it is possible, always provide and compare examples in C and JavaScript.

  • Feel free to use latest JavaScript/C language features if it makes an example simpler, concise and alike across the two languages.

  • Avoid using community packages in JavaScript examples. Stick to the JavaScript Global Objects as much as possible. Since the C Standard Library has a much smaller API surface, it is more acceptable to call out libraries for some functionality, should it be necessary for illustration (like rand for random number generation), but make sure they are mature, popular.

  • Make example code as self-contained as possible and runnable (unless the idea is to illustrate a compile-time or run-time error).

  • Maintain the general style of this guide, which is to avoid using you as if the reader is being told or instructed; use the third-person voice instead. For example, instead of saying, “You represent optional data in C with the pointers”, write instead, “C has the pointers that is used to represent optional data”.

Getting Started

Online C Compilers

The easiest way to get started with C without needing any local installation is to use online C compilers. They are minimal development front-ends that runs in the Web browser and allows writing and running C code.

Dev Container

The execution environment of the online C compilers has some limitations, such as total compilation/execution time, memory and networking so another option that does not require installing C would be to use a dev container. Like online C compilers, the dev container can be run directly in a Web browser using GitHub Codespaces or locally using Visual Studio Code.

Local Install

For a complete local installation of C compiler and its development tools, see the installation page on websites of different compilers.

Language

This sections compares JavaScript and C language features.

Scalar Types

The following table lists the primitive types in C and their equivalent in JavaScript:

CJavaScriptNote
bool (stdbool.h)boolean
charstringSee note 1.
char / signed charnumberSee note 2.
short intnumber
int / signed intnumber
long long intnumber/bigint
unsigned long long intnumber/bigint
LONG_LONG_MAX (limits.h)Number.MAX_SAFE_INTEGER
unsigned charnumber
unsigned short intnumber
unsigned intnumber
unsigned long long intnumber/bigint
unsigned long long intnumber/bigint
LONG_LONG_MAX (limits.h)Number.MAX_SAFE_INTEGER
floatnumber/bigdecimal
doublenumber/bigdecimal
number
nullnull
undefined
See note 3.

Notes:

  1. char in C and string in JavaScript have different definitions. In C, a char is 1 bytes wide, but in JavaScript, a character is 2 bytes wide and stores the character using the UTF-16 encoding. There is no char type equivalent in JavaScript, only string. For more information, see the C char documentation.
  2. There are only three number data type in JavaScript, number, which is essentially a floating point number. And the bigint type for storing numbers that exceed the range -(253 - 1) (Number.MIN_SAFE_INTEGER) to 253 - 1 (Number.MAX_SAFE_INTEGER). and the bigdecimal type for storing high-precision decimals.
  3. For historical reasons, JavaScript has two empty data types: null and undefined. undefined denotes a value that was never created, and null denotes a value that was created but intentionally left empty. See also:

Strings

Encoding issues with C language strings

In computers, characters are stored in binary encoding. A character set is a standard collection of encoded characters. In 1967, the American National Standards Institute (ANSI) released the ASCII character set that only supported the English alphabet, and then various countries and regions created various ASCII-compatible but incompatible character sets based on ASCII. These character sets are collectively known as multi-byte character sets (MBCS). In the 1990s, MBCS was the default standard for C character types and their associated functions. In 1991, the Unicode Consortium was formed to address the incompatibility of character sets, with the aim of creating a unified international character set standard. Initially, Unicode released the UTF-16 encoding standard, which is not ASCII compatible, and it has gradually and slowly gained support from the industry. Microsoft didn't release Windows 2000 with Unicode support until 2000.

However, on the Windows operating system platform, where compatibility is emphasized, the default standard for C character types and their associated functions is still the outdated MBCS (also known as narrow characters, functions end in A in the Windows API) rather than Unicode (at Microsoft, the word Unicode sometimes refers to UTF-16LE in code) (LE stands for Little Endian, which refers to the lower bytes first, The order in which the high-digit bytes are encoded after (also known as wide characters, functions end with W in the Windows API). For compatibility, Windows additionally defines the C character type 'wchar' and its associated functions for UTF-16LE strings, as well as the character type 'TCHAR' for both Unicode and non-Unicode compatibility (functions do not end with a capital A or W in the Windows API) and its related functions. This makes it difficult for Windows programmers. Later, more advanced UTF-8 encoding was released. Unix-like operating systems (e.g., Linux, FreeBSD), who is not very compatible, changed the default standard for C character types and their associated functions to UTF-8. Due to compatibility, and a lack of emphasis on UTF-8, Windows does not support UTF-8 as a standard for encoding programs internally. Windows programmers are still miserable.

The turning point came in 2015, when Microsoft's new CEO, Satya Nadella, took office, and he launched a strategy to embrace open source software. In the open-source world, people generally use Linux. At this point, character encoding becomes a big problem. So, in 2019, starting with Windows version 1903 (May 2019 Update), you can use manifests to have processes use UTF-8 as process code pages. In order to avoid conflicts with UTF-16LE, Microsoft has modified the MBCS related APIs to do this. But for compatibility reasons, the preferred program internal encoding on Windows is still UTF-16LE. In addition, Microsoft began to develop a terminal "Windows Terminal" that supports Unicode (including UTF-16LE, UTF-8, etc.) to solve this problem. Previously, Windows Terminal did not support Unicode characters. Now, Windows programmers can finally use UTF-8 as the default standard for C character types and their associated functions, as well as for the internal encoding of programs, by adding a manifest.

There is a character type in C: char.

The mapping of those to JavaScript is shown in the following table:

CJavaScriptNote
charstringsee Note 1.

There are differences in working with strings in C and JavaScript, but the equivalents above should be a good starting point. One of the differences is that C characters are using different encodes, but JavaScript strings are UTF-16 encoded.

Notes:

  1. JavaScript has only one string type, string. JavaScript has no pointer types.

JavaScript:

let str = "Hello, World!"; 
let str = new String("Hello, World!")

C:

char str[]= "Hello, World!"; 

String Literals

String literals in JavaScript String types and allocated on the heap. In Rust, they are char, has a global lifetime and does not get allocated on the heap; they're embedded in the compiled binary. JavaScript:

let str = "Hello, World!";

C:

char str[]= "Hello, World!"

JavaScript verbatim string literals are equivalent to Rust raw string literals.

JavaScript

let str = `Hello, \World/!`;

C

char *str = R"(Hello, \World/!)";

C UTF-8 string literals:

char str[] = u8"hello";

String Interpolation

JavaScript has a built-in string interpolation feature that allows you to embed expressions inside a string literal. The following example shows how to use string interpolation in JavaScript:

let name = "John";
let age = 42;
let str = `Person Name: ${name}, Age: ${age} `;

C does not have a built-in string interpolation feature. Instead, the printf function is used to format a string. The following example shows how to use string interpolation in C:

#include <stdio.h>

int main() {
    char name[] = "John";
    int age = 42;
    printf("Person Name: %s, Age: %d", name, age);
    return 0;
}

Custom classes and structs can also be interpolated in JavaScript due to the fact that the toString() method is available for each type as it inherits from object.

class Person {
    constructor({
        name,
        age
    }) {
        this.name = name;
        this.age = age;
        this.toString = function() {
            return `Person Name: ${name}, Age: ${age}`;
        }
    }

}
let person = new Person({
    name: "John",
    age: 42
});
console.log(person);

In C, there is no default formatting implemented/inherited for each type. We use structs instead of classes, and function pointers to simulate methods in classes.

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    char *name;
    int age;
    char* (*toString)(void);
} Person;

char* Person_toString() {
    return "Person Name: %s, Age: %d";
}

Person* new_Person(char* name, int age) {
    Person* person = (Person*)malloc(sizeof(Person));
    person->name = name;
    person->age = age;
    person->toString = Person_toString;
    return person;
}

int main() {
    Person* person = new_Person("John", 42);
    printf(person->toString(), person->name, person->age);
    free(person);
    return 0;
}

Text Replacing

C doesn't have a built-in string replacement feature, so replacing strings is very complex in C. Here's the code for a C function to replace a string:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/**
 * @brief Replace a substring in a string with another substring.
 * @param str The original string.
 * @param find The substring to find.
 * @param replace The substring to replace.
 * @param case_sensitive Flag to indicate case sensitivity (1 for case-sensitive, 0 for case-insensitive).
 * @param global_replace Flag to enable global replacement (1 for global, 0 for first occurrence only).
 * @return The modified string after replacement.
 */
char* replace_str(const char* str, const char* find, const char* replace, int case_sensitive, int global_replace) {
    char* result;
    int i, count = 0;
    int find_len = strlen(find);
    int replace_len = strlen(replace);

    for (i = 0; str[i] != '\0';) {
        if ((case_sensitive ? strncmp(&str[i], find, find_len) : strncasecmp(&str[i], find, find_len)) == 0) {
            count++;
            if (!global_replace) break;
            i += find_len;
        } else {
            i++;
        }
    }

    result = (char*)malloc(strlen(str) + count * (replace_len - find_len) + 1);

    i = 0;
    while (*str) {
        if ((case_sensitive ? strncmp(str, find, find_len) : strncasecmp(str, find, find_len)) == 0) {
            strcpy(&result[i], replace);
            i += replace_len;
            str += find_len;
            if (!global_replace) break;
        } else {
            result[i++] = *str++;
        }
    }
    result[i] = '\0';
    return result;
}

int main() {
    const char* original_str = "Hello, World!";
    const char* find_str = "World";
    const char* replacement_str = "C Language";

    char* new_str = replace_str(original_str, find_str, replacement_str, 0, 1); // Case-insensitive, global replacement
    printf("Original String: %s\n", original_str);
    printf("New String: %s\n", new_str);

    free(new_str);
    return 0;
}

Structured Types

Commonly used object and collection types in JavaScript and their mapping to C.

JavaScriptC
array(The type of an array varies with types)

Array

Fixed arrays are supported the same way in C as in JavaScript.

JavaScript:

let someArray = [1,2];

C:

int someArray[2] = {1, 2};

List

In C the equivalent of a array is an array.

JavaScript:

let something = ["a", "b"];
something.push("c");

c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char **something = malloc(2 * sizeof(char*));
    something[0] = "a";
    something[1] = "b";
    int size = 2;
    
    // Before adding an element
    for (int i = 0; i < size; i++) {
        printf("%s ", something[i]);
    }
    
    // Adding an element
    char *new_element = "c";
    size++;
    char **temp = realloc(something, size * sizeof(char*));
    if (temp != NULL) {
        something = temp;
        something[size - 1] = new_element;
    }
    
    // After adding an element
    printf("\n");
    for (int i = 0; i < size; i++) {
        printf("%s ", something[i]);
    }
    
    // Release the memory.
    free(something);
    
    return 0;
}

Tuples

JavaScript:

const let something = [1, 2];
console.log(`a = ${something[0]} b = ${something[1]}`);

In C, arrays are used to simulate the concept of tuples:

#include <stdio.h>

int main() {
    int something[] = {1, 2};
    printf("a = %d b = %d", something[0], something[1]);
    return 0;
}

// deconstruction supported
#include <stdio.h>

int main() {
    // Define a tuple
    struct Tuple {
        int a;
        int b;
    };

    // Initialize the tuple
    struct Tuple something = {1, 2};

    // Deconstruct tuples
    int a = something.a;
    int b = something.b;
    printf("a = %d b = %d\n", a, b);

    return 0;
}

NOTE: C tuple elements cannot be named. The only way to access a tuple element is by using the index of the element or deconstructing the tuple.

Dictionary

In C the equivalent of a Dictionary is a object.

JavaScript:

var something = {
    "Foo": "Bar",
    "Baz": "Qux"
};

something["hi"] = "there";

C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define INITIAL_CAPACITY 2

typedef struct {
    char* key;
    char* value;
} Entry;

typedef struct {
    Entry* entries;
    int capacity;
    int size;
} HashMap;

void insert(HashMap* map, char* key, char* value) {
    if (map->size >= map->capacity) {
        map->capacity *= 2;
        map->entries = realloc(map->entries, map->capacity * sizeof(Entry));
    }
    map->entries[map->size].key = strdup(key);
    map->entries[map->size].value = strdup(value);
    map->size++;
}

void printHashMap(HashMap* map) {
    printf("HashMap Contents:\n");
    for (int i = 0; i < map->size; i++) {
        printf("Key: %s, Value: %s\n", map->entries[i].key, map->entries[i].value);
    }
}


int main() {
    HashMap something;
    something.entries = malloc(INITIAL_CAPACITY * sizeof(Entry));
    something.capacity = INITIAL_CAPACITY;
    something.size = 0;

    insert(&something, "Foo", "Bar");
    insert(&something, "Baz", "Qux");
    insert(&something, "hi", "there");

    // Add any other actions here or print the code for the HashMap
    printHashMap(&something); 
    return 0;
}


See also:

Custom Types

The following sections discuss various topics and constructs related to developing custom types:

Classes

C doesn't have classes. It only has struct.

Records

C doesn't have any construct for authoring records.

Structures (struct)

In JavaScript, there is no direct concept of a structure, but you can use objects to model similar structures. Structures in C:

  • In C, struct simply defines the data/fields. Developers can encapsulate data by defining structs and manipulating that data with functions, which is similar to impl in rust.

  • They cannot be sub-classed.

  • They are allocated on stack by default.

In C, a struct is the primary construct for modeling any data structure (the other being an enum).

In C, structs do not need to implement traits, as C does not support the concept of traits in object-oriented programming.

#include <stdio.h>

struct Point {
    int x;
    int y;
};

int main() {
    struct Point p;
    p.x = 10;
    p.y = 20;

    printf("Point coordinates: (%d, %d)\n", p.x, p.y);

    return 0;
}

Value types in JavaScript are usually designed by a developer to be mutable. It's considered best practice speaking semantically, but the language does not prevent designing a struct that makes destructive or in-place modifications.

Since C doesn't have classes and consequently type hierarchies based on sub-classing, shared behaviour is achieved via traits and generics and polymorphism via virtual dispatch using trait objects.

In JavaScript:

class Rectangle {
    constructor(x1, y1, x2, y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    length() {
        return this.y2 - this.y1;
    }

    width() {
        return this.x2 - this.x1;
    }

    top_left() {
        return [this.x1, this.y1];
    }

    bottom_right() {
        return [this.x2, this.y2];
    }

    area() {
        return this.length() * this.width();
    }

    is_square() {
        return this.width() === this.length();
    }

    toString() {
        return `(${this.x1}, ${this.y1}), (${this.x2}, ${this.y2})`;
    }
}

const rect = new Rectangle(0, 0, 4, 4);
console.log(rect.area());
console.log(rect.toString());

The equivalent in C would be:

#include <stdio.h>

typedef struct {
    int x1, y1, x2, y2;
} Rectangle;

int length(Rectangle rect) {
    return rect.y2 - rect.y1;
}

int width(Rectangle rect) {
    return rect.x2 - rect.x1;
}

int area(Rectangle rect) {
    return length(rect) * width(rect);
}

int is_square(Rectangle rect) {
    return width(rect) == length(rect);
}

void display(Rectangle rect) {
    printf("(%d, %d), (%d, %d)\n", rect.x1, rect.y1, rect.x2, rect.y2);
}

int main() {
    Rectangle rect = {0, 0, 4, 4};
    
    printf("Length: %d\n", length(rect));
    printf("Width: %d\n", width(rect));
    printf("Area: %d\n", area(rect));
    printf("Is Square: %s\n", is_square(rect) ? "Yes" : "No");
    
    display(rect);
    
    return 0;
}

Since there is no inheritance in C, the way a type advertises support for some formatted representation is by defining a struct for Rectangle and a function to print its contents.:

#include <stdio.h>

typedef struct {
    int x;
    int y;
    int width;
    int height;
} Rectangle;

void displayRectangle(Rectangle rect) {
    printf("Rectangle = { x: %d, y: %d, width: %d, height: %d }\n", rect.x, rect.y, rect.width, rect.height);
}

int main() {
    Rectangle rect = {12, 34, 56, 78};
    displayRectangle(rect);
    return 0;
}

Interfaces

When creating an interface in C, developers often rely on structures to define the interface's layout. By encapsulating related data fields and function pointers within a structure, C programmers can simulate an interface-like behavior.

Here's a simple example to illustrate how an interface can be introduced in C:

#include <stdio.h>

// Define the interface structure
struct Interface {
    int (*getData)();  // Function pointer for getting data
    void (*displayData)();  // Function pointer for displaying data
};

// Implement functions for the interface
int getDataImplementation() {
    return 42;
}

void displayDataImplementation() {
    printf("Data: %d\n", getDataImplementation());
}

int main() {
    // Create an instance of the interface
    struct Interface myInterface = {getDataImplementation, displayDataImplementation};

    // Utilize the interface functions
    myInterface.displayData();

    return 0;
}

In this example, the Interface structure defines the layout of the interface with function pointers for getData and displayData. The getDataImplementation and displayDataImplementation functions serve as the concrete implementations of these interface functions.

By instantiating the Interface structure and assigning the appropriate function implementations, developers can achieve a form of interface-like behavior in C. This approach allows for abstraction and modularity in C programming, enabling better code organization and reusability.

Enumeration types (enum)

To convert a C enum to JavaScript, you can represent it using an object with key-value pairs.

const DayOfWeek = {
    Sunday: 0,
    Monday: 1,
    Tuesday: 2,
    Wednesday: 3,
    Thursday: 4,
    Friday: 5,
    Saturday: 6
};

C syntax for doing the same:

enum DayOfWeek
{
    Sunday = 0,
    Monday = 1,
    Tuesday = 2,
    Wednesday = 3,
    Thursday = 4,
    Friday = 5,
    Saturday = 6,
};

When dealing with an enum type in C, there is no inherent behavior or predefined functionality associated with instances of the enum.

In C, enums do not have built-in support for coercion to integral values. Developers need to manually implement functions to achieve this behavior. Here is an example of how to achieve similar functionality in C:

#include <stdio.h>

typedef enum {
    Sunday = 0,
    Monday = 1,
    Tuesday = 2,
    Wednesday = 3,
    Thursday = 4,
    Friday = 5,
    Saturday = 6
} DayOfWeek;

int main() {
    DayOfWeek dow = Wednesday;
    printf("Day of week = %d\n", dow);

    if (dow == Friday) {
        printf("Yay! It's the weekend!\n");
    }

    // Coerce to integer
    int dowInt = (int)dow;
    printf("Day of week = %d\n", dowInt);

    // Manually coerce back to DayOfWeek
    DayOfWeek newDow = (DayOfWeek)dowInt;
    printf("Day of week = %d\n", newDow);

    return 0;
}

In C, enums can be casted manually to integral types and vice versa. This approach gives developers control over the conversion process, similar to the Rust example provided.

In C, to convert an integral type to an enum, one can create a function that maps integral values to an enum:

#include <stdio.h>

typedef enum {
    Sunday = 0,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
} DayOfWeek;

DayOfWeek try_from_i32(int n) {
    if (n >= 0 && n <= 6) {
        return (DayOfWeek)n;
    } else {
        return -1; // Or any other error code indicating failure
    }
}

int main() {
    DayOfWeek dow = try_from_i32(5);
    if (dow != -1) {
        printf("Day of the week: %d\n", dow);
    } else {
        printf("Error: Invalid day\n");
    }

    dow = try_from_i32(50);
    if (dow != -1) {
        printf("Day of the week: %d\n", dow);
    } else {
        printf("Error: Invalid day\n");
    }

    return 0;
}

In this C code snippet, the try_from_i32 function maps the integer input to the corresponding DayOfWeek enum value if valid. It handles out-of-range values by returning an error code.

In C, enum types can be utilized to create union types, allowing for different variants to store variant-specific data. This concept is similar to Rust's discriminated union types. Below is an example illustrating the use of enum for union types in C:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

enum IpAddrTag {
    V4,
    V6
};

struct IpAddrV4 {
    uint8_t octet1;
    uint8_t octet2;
    uint8_t octet3;
    uint8_t octet4;
};

struct IpAddrV6 {
    char address[40]; // Assuming a maximum IPv6 address length
};

union IpAddr {
    struct IpAddrV4 v4;
    struct IpAddrV6 v6;
};

int main() {
    union IpAddr home;
    home.v4.octet1 = 127;
    home.v4.octet2 = 0;
    home.v4.octet3 = 0;
    home.v4.octet4 = 1;

    union IpAddr loopback;
    strcpy(loopback.v6.address, "::1");

    return 0;
}

This form of enum declaration does not exist in JavaScript, but it can be emulated with classes:

class IpAddr {
    constructor(v4, v6) {
        this.v4 = v4;
        this.v6 = v6;
    }
}

const home = new IpAddr([127, 0, 0, 1], null);
const loopback = new IpAddr(null, "::1");

Members

Constructors

In C programming, constructors are not explicitly defined. Instead, you can achieve similar functionality by using factory functions that create and initialize instances of a struct. These factory functions can be standalone functions or associated functions of the struct. Conventionally, if there is only one factory function for a struct, it is commonly named new.

#include <stdio.h>

typedef struct {
    int x1, y1, x2, y2;
} Rectangle;

Rectangle newRectangle(int x1, int y1, int x2, int y2) {
    Rectangle rect;
    rect.x1 = x1;
    rect.y1 = y1;
    rect.x2 = x2;
    rect.y2 = y2;
    return rect;
}

int main() {
    Rectangle myRect = newRectangle(0, 0, 100, 100);
    printf("Rectangle coordinates: (%d, %d), (%d, %d)\n", myRect.x1, myRect.y1, myRect.x2, myRect.y2);
    return 0;
}

Methods (static & instance-based)

C types (both enum and struct), can have static and instance-based methods.

#include <stdio.h>

typedef struct {
    int x1, y1, x2, y2;
} Rectangle;

Rectangle new_rectangle(int x1, int y1, int x2, int y2) {
    Rectangle rect;
    rect.x1 = x1;
    rect.y1 = y1;
    rect.x2 = x2;
    rect.y2 = y2;
    return rect;
}

int length(Rectangle *rect) {
    return rect->y2 - rect->y1;
}

int width(Rectangle *rect) {
    return rect->x2 - rect->x1;
}

int area(Rectangle *rect) {
    return length(rect) * width(rect);
}

int main() {
    Rectangle rect = new_rectangle(0, 0, 4, 3);
    printf("Length: %d\n", length(&rect));
    printf("Width: %d\n", width(&rect));
    printf("Area: %d\n", area(&rect));
    return 0;
}

Constants

A type in C can have constants. However, the most interesting aspect to note is that C allows a type instance to be defined as a constant too:

struct Point {
    int x;
    int y;
};

const struct Point ZERO = {0, 0};

In JavaScript, you need to define a Point class, and then, simulate the construction behavior in Rust by setting a static property ZERO directly on the Point class.

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

Point.ZERO = new Point(0, 0);

Events

C has no built-in support for type members to adverstise and fire events.

Properties

In C, there are no built-in properties. To mimic property-like behavior, the user can use getter and setter functions.

#include <stdio.h>

typedef struct {
    int x1, y1, x2, y2;
} Rectangle;

Rectangle newRectangle(int x1, int y1, int x2, int y2) {
    Rectangle rect = {x1, y1, x2, y2};
    return rect;
}

// Getter functions
int getX1(Rectangle *rect) { return rect->x1; }
int getY1(Rectangle *rect) { return rect->y1; }
int getX2(Rectangle *rect) { return rect->x2; }
int getY2(Rectangle *rect) { return rect->y2; }

// Setter functions
void setX1(Rectangle *rect, int val) { rect->x1 = val; }
void setY1(Rectangle *rect, int val) { rect->y1 = val; }
void setX2(Rectangle *rect, int val) { rect->x2 = val; }
void setY2(Rectangle *rect, int val) { rect->y2 = val; }

// Computed properties
int length(Rectangle *rect) { return rect->y2 - rect->y1; }
int width(Rectangle *rect) { return rect->x2 - rect->x1; }
int area(Rectangle *rect) { return length(rect) * width(rect); }

int main() {
    Rectangle rect = newRectangle(0, 0, 10, 20);
    printf("Area of the rectangle: %d\n", area(&rect));
    return 0;
}

Extension Methods

In JavaScript, you can use prototype to add new methods to existing classes. This approach allows you to add new behavior to an existing class without changing the existing class definition:

//JavaScript doesn't have a StringBuilder class. This code is only used to demonstrate adding a new method to an existing class.
class StringBuilder {
    constructor(initialString) {
        this.value = initialString;
    }

    toString() {
        return this.value;
    }
}
StringBuilder.prototype.wrap = function (left, right) {
        this.value = left + this.value + right;
    }
const sb = new StringBuilder("Hello, World!");
sb.wrap(">>> ", " <<<");
console.log(sb.toString()); 

C does not have built-in support for extension methods or traits. To achieve similar functionality in C, one can use function pointers or structures to mimic extension methods. Here's a simplified example in C to demonstrate extending a type with a method:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Define the StringExt structure before using it.
typedef struct StringExt {
    char* data;
    // The wrap function should take a pointer to StringExt to modify the original structure.
    void (*wrap)(struct StringExt*, const char*, const char*);
} StringExt;

// The wrap function now correctly takes a pointer to a StringExt structure.
void wrap(struct StringExt* self, const char* left, const char* right) {
    if (self == NULL || self->data == NULL || left == NULL || right == NULL) {
        return; // Safety check to avoid dereferencing NULL pointers.
    }
    // Allocate memory for the new string, including space for the null terminator.
    char* temp = malloc(strlen(left) + strlen(self->data) + strlen(right) + 1);
    if (temp == NULL) {
        return; // Check for failed memory allocation.
    }
    // Concatenate the strings in the correct order.
    strcpy(temp, left);
    strcat(temp, self->data);
    strcat(temp, right);
    // Free the old data and update the StringExt structure with the new data.
    free(self->data);
    self->data = temp;
}

int main() {
    // Declare the StringExt variable as an actual structure, not just a type.
    StringExt s = { .data = strdup("Hello, World!"), .wrap = wrap };

    // Call the wrap function with the address of the StringExt structure.
    s.wrap(&s, ">>> ", " <<<");
    printf("%s\n", s.data); // Should now print: >>> Hello, World! <<<

    free(s.data); // Free the allocated memory for the data string.
    return 0;
}

Visibility/Access modifiers

In JavaScript, there is no explicit visibility modifiers, but similar functionality can be achieved with some conventions.

In C, visibility and access control are primarily achieved through the use of header files and the concept of translation units. By declaring functions and variables in header files and including them in source files, C provides a way to control visibility. To mimic private members, one can use static variables or functions within a source file, limiting their scope to that file. For public visibility, declaring functions and variables in header files and including those headers in multiple source files allows for shared access. While C lacks explicit modifiers like pub in Rust, the structuring of code using header files and source files provides a similar level of control over visibility and access.

Mutability

When designing a type in JavaScript, it is not the responsiblity of the developer to decide whether the a type is mutable or immutable; whether it supports destructive or non-destructive mutations. In C, mutability is expressed on methods through the type of the parameters as shown in the example below:

#include <stdio.h>

struct Point {
    int x;
    int y;
};

struct Point new_point(int x, int y) {
    struct Point p;
    p.x = x;
    p.y = y;
    return p;
}

int get_x(struct Point *p) {
    return p->x;
}

int get_y(struct Point *p) {
    return p->y;
}

void set_x(struct Point *p, int val) {
    p->x = val;
}

void set_y(struct Point *p, int val) {
    p->y = val;
}

int main() {
    struct Point p = new_point(3, 4);
    printf("Point coordinates: (%d, %d)\n", get_x(&p), get_y(&p));
    set_x(&p, 7);
    set_y(&p, 8);
    printf("Updated point coordinates: (%d, %d)\n", get_x(&p), get_y(&p));
    return 0;
}

In JavaScript, use ES6's destructuring assignment and object extension syntax to implement non-destructive mutation:

class Point {
    constructor(X, Y) {
        this.X = X;
        this.Y = Y;
    }
}

let pt = new Point(123, 456);
pt = { ...pt, X: 789 };
console.log(pt); // prints: Point { X = 789, Y = 456 }

There is no with in C, but to emulate something similar in C, it has to be baked into the type's design:

#include <stdio.h>

typedef struct {
    int x;
    int y;
} Point;

Point new_point(int x, int y) {
    Point p;
    p.x = x;
    p.y = y;
    return p;
}

int get_x(Point p) {
    return p.x;
}

int get_y(Point p) {
    return p.y;
}

Point set_x(Point p, int val) {
    p.x = val;
    return p;
}

Point set_y(Point p, int val) {
    p.y = val;
    return p;
}

int main() {
    Point p = new_point(3, 4);
    printf("Initial Point: x=%d, y=%d\n", get_x(p), get_y(p));

    p = set_x(p, 7);
    p = set_y(p, 9);
    printf("Modified Point: x=%d, y=%d\n", get_x(p), get_y(p));

    return 0;
}

In JavaScript, classes are used to simulate structs, and destructuring objects is assigned to implement something like with.

class Point {
    constructor(x, y) {
        this.X = x;
        this.Y = y;
    }

    toString() {
        return `(${this.X}, ${this.Y})`;
    }
}

let pt = new Point(123, 456);
console.log(pt.toString()); // prints: (123, 456)
pt = { ...pt, X: 789 };
console.log(pt.toString()); // prints: (789, 456)

C has another syntax that may seem similar:

#include <stdio.h>

typedef struct {
    int x;
    int y;
} Point;

Point createPoint(int x, int y) {
    Point pt;
    pt.x = x;
    pt.y = y;
    return pt;
}

void printPoint(Point pt) {
    printf("Point { x: %d, y: %d }\n", pt.x, pt.y);
}

Point set_x(Point p, int val) {
    p.x = val;
    return p;
}

Point set_y(Point p, int val) {
    p.y = val;
    return p;
}

int get_x(Point p) {
    return p.x;
}

int get_y(Point p) {
    return p.y;
}

int main() {
    Point p = createPoint(3, 4);
    printf("Initial Point: x=%d, y=%d\n", get_x(p), get_y(p));

    p = set_x(p, 7);
    p = set_y(p, 9);
    printf("Modified Point: x=%d, y=%d\n", get_x(p), get_y(p));

    return 0;
}

Local Functions

C offer local functions,but C functions cannot use variables from their surrounding lexical scope.

Lambda and Closures

C does not directly support higher-order functions. However, function pointers in C can be used to achieve similar behavior.

#include <stdio.h>

typedef int (*func_ptr)(int);

int do_twice(func_ptr f, int arg) {
    return f(arg) + f(arg);
}

int add_one(int x) {
    return x + 1;
}

int main() {
    int answer = do_twice(add_one, 5);
    printf("The answer is: %d\n", answer); // Prints: The answer is: 12
    return 0;
}

In JavaScript:

function doTwice(f, arg) {
    return f(arg) + f(arg);
}

function main() {
    const answer = doTwice(x => x + 1, 5);
    console.log(`The answer is: ${answer}`); // Prints: The answer is: 12
}

main();

In C programming, closures can be simulated using function pointers and structures. By encapsulating data and functions within a structure, C can achieve a form of closures. This approach allows functions to access variables from their enclosing scope even after the scope has exited. The structure holds the data and function pointers, enabling the functions to operate on the enclosed data. While C does not have native closure support like some other languages, this workaround provides a mechanism for achieving similar functionality. By leveraging function pointers and structures, C programmers can implement closures effectively within their code.

Variables

Consider the following example around variable assignment in JavaScript:

let x = 5;

And the same in C:

int x = 5;

C is not type-safe: the compiler guarantees that the value stored in a variable is always of the designated type. The example can be simplified by using the compiler's ability to automatically infer the types of the variable. In JavaScript:

let x = 5;

In C:

auto x = 5;

When expanding the first example to update the value of the variable (reassignment), the behavior of JavaScript and Rust differ:

let x = 5;
x = 6;
console.log(x); // 6

In C, the identical statement will compile:

#include <stdio.h>

int main() {
    int x = 5;
    x = 6; // Variable 'x' is mutable in C
    printf("%d", x);
    return 0;
}

In C, variables are mutable by default. Once a value is bound to a name, the variable's value can be changed:

#include <stdio.h>

int main() {
    int x = 5;
    x = 6;
    printf("%d", x);
    return 0;
}

In C, the concept of variable shadowing is not directly supported. However, a similar effect can be achieved by declaring a new variable with the same name in a nested scope:

#include <stdio.h>

int main() {
    int x = 5;
    {
        int x = 6;
        printf("%d", x); // Output: 6
    }
    return 0;
}

JavaScript also supports shadowing, e.g. locals can shadow fields and type members can shadow members from the base type. In Rust, the above example demonstrates that shadowing also allows to change the type of a variable without changing the name, which is useful if one wants to transform the data into different types and shapes without having to come up with a distinct name each time.

See also:

Namespaces

In JavaScript, there is no such thing as a namespace. Instead, JavaScript developers use sub-objects or conventions to implement namespace-like functionality.

In C, there is no direct support for namespaces like in C++. However, it is possible to achieve a similar effect by using a naming convention to simulate namespaces. One common approach is to prefix your functions and variables with a unique identifier to mimic the behavior of namespaces.

Equality

When comparing for equality in JavaScript, this refers to testing for equivalence insome cases (also known as value equality), and in other cases it refers to testing for reference equality, which tests whether two variables refer to the same underlying object in memory. In JavaScript, while there is no syntax for explicitly custom types, custom types can be simulated through constructors and prototypes. Constructors allow you to create objects with specific properties and methods, and you can use prototypes to implement inheritance and shared methods. Every "custom type" can be compared for equality because it inherits from object.

For example, when comparing for equivalence and reference equality in JavaScript:

class Point {
    constructor(X, Y) {
        this.X = X;
        this.Y = Y;
    }
    
    equals(other) {
        return this.X === other.X && this.Y === other.Y;
    }
}

const a = new Point(1, 2);
const b = new Point(1, 2);
const c = a;

console.log(a.equals(b)); // (1) true
console.log(a.equals(new Point(2, 2))); // (1) false
console.log(a === b); // (2) false
console.log(a === c); // (2) true
  1. In JavaScript, classes are used to implement equals methods to compare the equality of values.
  2. For the comparison of reference equality, using the === operator to check if the variable points to the same object in memory.

Equivalently in C:

#include <stdio.h>

typedef struct {
    int x;
    int y;
} Point;

int point_equals(Point a, Point b) {
    return a.x == b.x && a.y == b.y;
}

int main() {
    Point a = {1, 2};
    Point b = {1, 2};
    Point c = a;

    printf("%d\n", point_equals(a, b)); // true
    printf("%d\n", point_equals(a, b)); // true
    printf("%d\n", point_equals(a, (Point){2, 2})); // false
    printf("%d\n", point_equals(a, c)); // true

    return 0;
}

Generics

Generics provide a way to create definitions for types and methods that can be parameterized over other types. This improves code reuse, type-safety and performance (e.g. avoid run-time casts). Consider the following example of a generic type that adds a timestamp to any value. However, JavaScript does not have the concept of generics.

class Timestamped {
    constructor(value) {
        this.Timestamp = new Date();
        this.Value = value;
    }
}

C does not have built-in support for generics. In C, preprocessor macros or void pointers are typically used to achieve a similar effect.

Here's a simplified example in C that mimics the Rust code using a void pointer to achieve a generic-like behavior:

#include <stdio.h>
#include <time.h>

typedef struct {
    void* value;
    time_t timestamp;
} Timestamped;

Timestamped new(void* value) {
    Timestamped ts;
    ts.value = value;
    ts.timestamp = time(NULL);
    return ts;
}

int main() {
    int intValue = 42;
    Timestamped intTimestamped = new(&intValue);

    char charValue = 'A';
    Timestamped charTimestamped = new(&charValue);

    return 0;
}

Generic type constraints

JavaScript has no concept of generics, and it is a weakly typed scripting language that makes it impossible to add type constraints to it.

class Timestamped {
    constructor(value) {
        this.value = value;
        this.timestamp = Date.now();
    }

    equals(other) {
        return this.value === other.value && this.timestamp === other.timestamp;
    }
}

The same can be achieved in C:

#include <stdio.h>
#include <time.h>

typedef struct {
    void* value;
    time_t timestamp;
} Timestamped;

Timestamped new(void* value) {
    Timestamped ts;
    ts.value = value;
    ts.timestamp = time(NULL);
    return ts;
}

int equal(Timestamped* ts1, Timestamped* ts2) {
    return ts1->value == ts2->value && ts1->timestamp == ts2->timestamp;
}

int main() {
    void* val = NULL;
    Timestamped ts1 = new(val);
    Timestamped ts2 = new(val);

    if (equal(&ts1, &ts2)) {
        printf("Timestamped values are equal.\n");
    } else {
        printf("Timestamped values are not equal.\n");
    }

    return 0;
}

Polymorphism

C does not support classes and sub-classing therefore polymorphism can't be achieved in an identical manner to JavaScript.

See also:

Inheritance

As explained in structures section, C does not provide (class-based) inheritance. In C, there is no direct support for traits or supertraits. To achieve similar functionality in C, one can use structures and function pointers. By defining a structure that holds function pointers to shared behaviors and then implementing those behaviors in separate functions, one can simulate trait-like behavior in C. This approach allows for defining relationships between different structs by sharing common behavior through function pointers within the structs.

Exception Handling

In JavaScript, an exception should always be an Error object or an instance of an Error subclass. Exceptions are thrown if a problem occurs in a code section. A thrown exception is passed up the stack until the application handles it or the program terminates.

In C, error handling is typically done using return values or error codes. To convert the Rust code snippet provided to C, one would need to implement error handling using return values or custom error structures.

Custom error types

An example on how to create user-defined exceptions:

class EmployeeListNotFoundException extends Error {
    constructor(message) {
        super(message);
        this.name = 'EmployeeListNotFoundException';
    }
}

In C, one can achieve similar error handling by defining custom error structures and functions.

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    const char* message;
} EmployeeListNotFound;

const char* EmployeeListNotFound_message(EmployeeListNotFound* err) {
    return err->message;
}

void EmployeeListNotFound_free(EmployeeListNotFound* err) {
    free(err);
}

int main() {
    EmployeeListNotFound* err = (EmployeeListNotFound*)malloc(sizeof(EmployeeListNotFound));
    err->message = "Could not find employee list.";

    // Example usage
    printf("%s\n", EmployeeListNotFound_message(err));

    EmployeeListNotFound_free(err);

    return 0;
}

In C, the equivalent of the JavaScript Error.cause property can be achieved by defining a custom error structure that includes a field to store the error cause or source. Here is a simplified example:

#include <stdio.h>

typedef struct {
    const char* cause; // Field to store the error cause/source
} Error;

int main() {
    Error customError;
    customError.cause = "Custom error message";
    
    printf("Error Cause: %s\n", customError.cause);
    
    return 0;
}

Raising exceptions

To raise an error in JavaScript, throw an error:

function throwIfNegative(value) {
    if (value < 0) {
        throw new Error('Value cannot be negative');
    }
}

To mimic recoverable errors in C, an enum is used to define custom result types. The function error_if_negative checks if the input value is negative and returns the appropriate result. The main function demonstrates how to use this error handling mechanism in C.

#include <stdio.h>

typedef enum {
    OK,
    ERR
} Result;

Result error_if_negative(int value) {
    if (value < 0) {
        return ERR;
    } else {
        return OK;
    }
}

int main() {
    int input = -5;
    Result result = error_if_negative(input);

    if (result == ERR) {
        printf("Error: Specified argument was out of the range of valid values. (Parameter 'value')\n");
    } else {
        printf("No error. Value is non-negative.\n");
    }

    return 0;
}

In C, a way to handle unrecoverable errors is typically achieved using the abort() function from the <stdlib.h> library.

#include <stdio.h>
#include <stdlib.h>

void panic_if_negative(int value) {
    if (value < 0) {
        fprintf(stderr, "Specified argument was out of the range of valid values. (Parameter 'value')\n");
        abort();
    }
}

int main() {
    //Main code logic here
    return 0;
}

Error propagation

In JavaScript, exceptions are passed up until they are handled or the program terminates. In Rust, unrecoverable errors behave similarly, but handling them is uncommon.

Recoverable errors, however, need to be propagated and handled explicitly. Their presence is always indicated by the Rust function or method signature. Catching an exception allows you to take action based on the presence or absence of an error in JavaScript:

//JavaScript doesn't have a file system in it. People often implement file systems using the BrowserFS library that mimic Node.js APIs.
function write() {
    try {
        fs.writeFileSync('file.txt', 'content');
    } catch (error) {
        console.log('Writing to file failed.');
    }
}

In C, error handling is typically done using return values rather than exceptions. One would need to use functions that return error codes to indicate success or failure instead of throwing exceptions.

#include <stdio.h>

int write() {
    FILE *file = fopen("file.txt", "w");
    if (file == NULL) {
        printf("Error opening file.\n");
        return -1; // Return an error code
    }

    if (fprintf(file, "content") < 0) {
        printf("Error writing to file.\n");
        fclose(file);
        return -2; // Return a different error code
    }

    fclose(file);
    return 0; // Return 0 for success
}

int main() {
    int result = write();
    if (result != 0) {
        printf("Writing to file failed.\n");
    }
    return 0;
}

C does not have a built-in operator like Rust's ? for error propagation. In C, error handling is typically done using return values or error codes.

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file = fopen("file.txt", "w");
    if (file == NULL) {
        perror("Error opening file");
        return -1; // Error code for file opening failure
    }

    if (fwrite("content", sizeof(char), 7, file) != 7) {
        perror("Error writing to file");
        fclose(file);
        return -2; // Error code for incomplete write
    }

    fclose(file);
    return 0; // Success
}

Stack traces

In C, handling unrecoverable errors can be achieved using the abort() function, which terminates the program immediately. For recoverable errors, C does not have built-in support for backtraces. However, it is possible to implement a basic form of error handling using return codes or custom error structures to indicate and handle errors in a recoverable manner. To backtrace, developers often resort to manual logging or stack tracing techniques in C.

Nullability and Optionality

In JavaScript, null is often used to represent a value that is missing, absent or logically uninitialized. For example:

let some = 1;
let none = null;

Rust has no null and consequently no nullable context to enable. Optional or missing values are instead represented by [Option<T>][option]. The equivalent of the JavaScript code above in Rust would be:

#include <stddef.h>
//...
int some = 1;
int *none = NULL;

Control flow with optionality

In JavaScript, you may have been using if/else statements for controlling the flow when using nullable values.

let max = 10;
if (max !== null && max !== undefined) {
    let someMax = max;
    console.log(`The maximum is ${someMax}.`); // Output:The maximum is 10.
}

C does not have built-in support for null or undefined values like JavaScript. Instead, it typically uses specific values or flags to indicate absence or special conditions:

#include <stdio.h>

int main() {
    int max = 10;
    if (max != 0) {
        int someMax = max;
        printf("The maximum is %d.\n", someMax); // Output: The maximum is 10.
    }
    return 0;
}

Null-conditional operators

The null-conditional operators (?.) make dealing with null in JavaScript more ergonomic.

In C language, there is no direct equivalent to the null-conditional operator ?. found in JavaScript. To handle similar scenarios in C, one can use conditional statements to check for null pointers before accessing members.

let some = "Hello, World!";
let none = null;
console.log(some?.length); // 13
console.log(none?.length); // undefined
#include <stdio.h>
#include <string.h>

int main() {
    char* some = "Hello, World!";
    char* none = NULL;

    printf("%d\n", (some != NULL) ? (int)strlen(some) : -1);
    printf("%d\n", (none != NULL) ? (int)strlen(none) : -1);

    return 0;
}

Null-coalescing operator

The null-coalescing operator (??) is typically used to default to another value when a nullable is null:

let some = 1;
let none = null;
console.log(some ?? 0); // 1
console.log(none ?? 0); // 0

In C, there is no direct equivalent to the null-coalescing operator (??) as in JavaScript. However, it is possible to achieve similar functionality using conditional operators.

#include <stdio.h>

int main() {
    int some = 1;
    int *none = NULL;
    
    printf("%d\n", some); // 1
    printf("%d\n", none != NULL ? *none : 0); // 0
    
    return 0;
}

Null-forgiving operator

In C, there is no direct equivalent to the null-forgiving operator (!) found in languages like C# or Rust. To handle null pointers or avoid null references, C programmers typically use conditional statements or pointer checks to ensure safe memory access. Unlike Rust, C does not have built-in mechanisms for static flow analysis to handle null values implicitly. Therefore, developers need to implement explicit null checks and error handling in C code to manage potential null pointer exceptions effectively.

Discards

Discards express to the compiler and others to ignore the results (or parts) of an expression.

There are multiple contexts where to apply this, for example as a basic example, to ignore the result of an expression. JavaScript doesn't have discards, but you can call a function without assigning a value to any variable to emulate discards. In JavaScript this looks like:

city.getCityInformation(cityName);

In C, discarding the result of a function call is typically achieved by assigning the result to a variable and not using that variable further. In C, casting the result to (void) indicates to the compiler that the return value is intentionally being ignored.

(void)city_get_city_information(city_name);

Discards are also applied for deconstructing "tuples" in JavaScript:

const [_, second] = ["first", "second"];

In C, tuple deconstruction like in JavaScript can be achieved using a similar approach. However, C does not have built-in tuple types like JavaScript. To mimic tuple deconstruction, one can use arrays or structures:

#include <stdio.h>

int main() {
    char* values[] = {"first", "second"};
    char* second = values[1];

    printf("Second value: %s\n", second);

    return 0;
}

To achieve struct destructuring similar to Rust in C, one can use a similar approach by defining a struct and then accessing its members selectively.

#include <stdio.h>

struct Point {
    int x;
    int y;
    int z;
};

int main() {
    struct Point origin = {0, 0, 0};

    switch (origin.x) {
        case 0:
            printf("x is %d\n", origin.x);
            break;
    }

    return 0;
}

When pattern matching, it is often useful to discard or ignore part of a matching expression. But since there are no discards in JavaScript, and the switch statement of js cannot be used in the same way as rust, you have to emulate this feature in an awkward way:

const _ = ("first", "second");
const result = (_ => {
    switch(true) {
        case _.includes("first"):
            return "first element matched";
        default:
            return "first element did not match";
    }
})();

console.log(result);

and again, in C:

#include <stdio.h>
#include <string.h>

int main() {
    const char* first = "first";
    const char* second = "second";
    const char* result;

    if (strcmp(first, "first") == 0) {
        result = "first element matched";
    } else {
        result = "first element did not match";
    }

    printf("%s\n", result);

    return 0;
}

Conversion and Casting

C is statically-typed at compile time. Hence, after a variable is declared, assigning a value of a value of a different type (unless it's implicitly convertible to the target type) to the variable is prohibited. There are several ways to convert types in C.

Implicit conversions

Implicit conversions exist in JavaScript as well as in C (called type coercions). Consider the following example:

let intNumber = 1;
let longNumber = intNumber;

In C:

#include <stdio.h>

int main() {
    int int_number = 1;
    long long_number = int_number; // No error in C
    printf("int_number: %d, long_number: %ld\n", int_number, long_number);
    return 0;
}

By assigning the address of s to t, it is possible to achieve a similar implicit conversion in C:

void bar() {
    const char *s = "hi";
    const char *t = s;
}

See also:

  • [Deref coercion]

Explicit conversions

If converting could cause a loss of information, JavaScript requires explicit conversions using a casting expression:

let a = 1.2;
let b = parseInt(a);

C does not handle exceptions during conversions:

#include <stdio.h>

int main() {
    int int_number = 1;
    long long_number = (long)int_number;
    
    // Additional code for demonstration
    printf("Integer: %d\n", int_number);
    printf("Long: %ld\n", long_number);
    
    return 0;
}

Custom conversion

In C, type conversion is typically achieved through explicit casting:

#include <stdio.h>
#include <string.h>

typedef struct {
    char value[100];
} MyId;

void fromMyIdToString(MyId myId, char* result) {
    strcpy(result, myId.value);
}

int main() {
    MyId myId;
    strcpy(myId.value, "id");

    char result[100];
    fromMyIdToString(myId, result);

    printf("%s\n", result);

    return 0;
}

Operator overloading

JavaScript doesn't support operator overloading. Consider the following example in JavaScript:

class Fraction {
    constructor(numerator, denominator) {
        this.numerator = numerator;
        this.denominator = denominator;
    }

    static add(a, b) {
        return new Fraction(a.numerator * b.denominator + b.numerator * a.denominator, a.denominator * b.denominator);
    }

    toString() {
        return `${this.numerator}/${this.denominator}`;
    }
}

console.log(Fraction.add(new Fraction(5, 4), new Fraction(1, 2)).toString());  // Output "14/8"

In C, operator overloading is not directly supported. However, it is possible to achieve similar functionality by defining functions that mimic operator behavior.

#include <stdio.h>

typedef struct {
    int numerator;
    int denominator;
} Fraction;

Fraction addFractions(Fraction f1, Fraction f2) {
    Fraction result;
    result.numerator = f1.numerator * f2.denominator + f2.numerator * f1.denominator;
    result.denominator = f1.denominator * f2.denominator;
    return result;
}

int main() {
    Fraction f1 = {5, 4};
    Fraction f2 = {1, 2};
    
    Fraction result = addFractions(f1, f2);
    
    printf("%d/%d\n", result.numerator, result.denominator); // Output: 14/8
    
    return 0;
}

Documentation Comments

A third-party tool called JSDoc provides a mechanism to document the API for types using a comment syntax. JSDoc includes a Markdown plugin that automatically converts Markdown-formatted text to HTML. The comment contains structured data representing the comments and the API signatures. Other tools can process that output to provide human-readable documentation in a different form. A simple example in JavaScript:

public class MyClass {}
/**
 * This is a document comment for `MyClass`.
 * @class
 */
class MyClass {}

C lacks a standardized way to generate documentation from comments like JSDoc, developers can adopt tools like Doxygen to extract structured comments and generate documentation from C code. Doxygen interprets specially formatted comments to produce documentation.Doxygen serves as the tool for generating documentation. In C, Doxygen uses a specific syntax to create documentation comments. For instance, in C using Doxygen:

/* 
 * This is a comment for the MyStruct struct.
 */
struct MyStruct {
    // Members of the struct
};

In JSDoc, the equivalent to Doxygen is jsdoc.

See also:

Memory Management

In C, memory management is a crucial aspect of programming as it allows developers to allocate and deallocate memory dynamically. Unlike high-level languages with built-in garbage collection mechanisms, C requires manual memory management.

In JavaScript, there is no concept of ownership of memory beyond the GC roots (static fields, local variables on a thread's stack, CPU registers, handles, etc.). It is the GC that walks from the roots during a collection to detemine all memory in use by following references and purging the rest. When designing types and writing code, a JavaScript developer can remain oblivious to ownership, memory management and even how the garbage collector works for the most part, except when performance-sensitive code requires paying attention to the amount and rate at which objects are being allocated on the heap. In contrast, Rust's ownership rules require the developer to explicitly think and express ownership at all times and it impacts everything from the design of functions, types, data structures to how the code is written. On top of that, Rust has strict rules about how data is used such that it can identify at compile-time, data [race conditions] as well as corruption issues (requiring thread-safety) that could potentially occur at run-time. This section will only focus on memory management and ownership.

In C, developers need to manage memory explicitly.

#include <stdio.h>
#include <stdlib.h>

struct Point {
    int x;
    int y;
};

int main() {
    struct Point* a = (struct Point*)malloc(sizeof(struct Point));
    a->x = 12;
    a->y = 34;

    struct Point* b = a; // b now points to the same memory as a

    printf("%d, %d\n", a->x, a->y);

    free(a); // Freeing the memory explicitly

    return 0;
}

In C, there is no explicit concept of ownership. When a is assigned to b, a copy of the Point struct is made, and both a and b can be used independently. Memory management in C is manual, and there is no automatic dropping of resources like in Rust. Therefore, in the C version, the point behind b is not explicitly dropped as in Rust.

#include <stdio.h>

typedef struct {
    int x;
    int y;
} Point;

int main() {
    Point a = {12, 34}; // point owned by a
    Point b = a;        // b owns the point now
    printf("%d, %d\n", b.x, b.y); // ok, uses b
    return 0;
} // point behind b is not explicitly dropped in C

In C, the equivalent concept to execute code when an instance is dropped is typically achieved using a combination of a struct and functions.

In C, the concept of dropping an object, can be achieved through manual memory management.

In C, a static variable retains its value throughout the program's execution.

In JavaScript, references are shared freely without much thought.

#include <stdio.h>
#include <stdlib.h>

struct Point {
    int x;
    int y;
};

void point_drop(struct Point* self) {
    printf("Point dropped!\n");
    free(self);
}

int main() {
    struct Point* a = (struct Point*)malloc(sizeof(struct Point));
    a->x = 12;
    a->y = 34;

    struct Point* b = a; // share with b
    printf("a = %d, %d\n", a->x, a->y); // okay to use a
    printf("b = %d, %d\n", b->x, b->y);

    point_drop(a);
    return 0;
}

// prints:
// a = 12, 34
// b = 12, 34
// Point dropped!

In C, there are no smart pointers. The equivalent concept in C would involve manual memory management using functions like malloc and free.

struct Point {
    int x;
    int y;
};

struct Point* stack_point = (struct Point*)malloc(sizeof(struct Point));
stack_point->x = 12;
stack_point->y = 34;

struct Point* heap_point = (struct Point*)malloc(sizeof(struct Point));
heap_point->x = 12;
heap_point->y = 34;

Resource Management

Previous section on memory management explains the differences between JavaScript and C when it comes to GC. It is highly recommended to read it.

This section is limited to providing an example of a fictional database connection involving a SQL connection to be properly closed/disposed/dropped.

class DatabaseConnection {
    constructor(connectionString) {
        this.connectionString = connectionString;
    }

    closeConnection() {
        // Implementation to close the connection
    }
}

// ...closing connection...
DatabaseConnection.prototype.close = function() {
    this.closeConnection();
    console.log(`Closing connection: ${this.connectionString}`);
};

// Create instances of DatabaseConnection
const db1 = new DatabaseConnection("Server=A;Database=DB1");
const db2 = new DatabaseConnection("Server=A;Database=DB2");

// ...code for making use of the database connection...
// "Dispose" of "db1" and "db2" when their scope ends
#include <stdio.h>
#include <stdlib.h>

// Define a structure to represent DatabaseConnection
typedef struct {
    char* connectionString;
} DatabaseConnection;

// Function to create a new DatabaseConnection instance
DatabaseConnection* createDatabaseConnection(const char* connectionString) {
    DatabaseConnection* db = (DatabaseConnection*)malloc(sizeof(DatabaseConnection));
    db->connectionString = strdup(connectionString);
    return db;
}

// Function to close the connection
void closeConnection(DatabaseConnection* db) {
    // Implementation to close the connection
    printf("Closing connection: %s\n", db->connectionString);
    free(db->connectionString);
    free(db);
}

int main() {
    // Create instances of DatabaseConnection
    DatabaseConnection* db1 = createDatabaseConnection("Server=A;Database=DB1");
    DatabaseConnection* db2 = createDatabaseConnection("Server=A;Database=DB2");

    // ...code for making use of the database connection...

    // "Dispose" of "db1" and "db2" when their scope ends
    closeConnection(db1);
    closeConnection(db2);

    return 0;
}

Threading

The C standard library supports threading, synchronisation and concurrency. Also the language itself and the standard library do have basic support for the concepts, a lot of additional functionality is provided by third party libraries and will not be covered in this document.

For threading in C, the pthread library is commonly used:

#include <stdio.h>
#include <pthread.h> // Requires GCC

void *thread_function(void *arg) {
    // Thread logic here
    return NULL;
}

int main() {
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, thread_function, NULL);
    
    // Main program logic here
    
    pthread_join(thread_id, NULL);
    
    return 0;
}

For synchronization and concurrency in C, mechanisms like mutexes and semaphores can be utilized. Here is a basic example using a mutex for synchronization:

#include <stdio.h>
#include <pthread.h> // Requires GCC

pthread_mutex_t mutex;

void *thread_function(void *arg) {
    pthread_mutex_lock(&mutex);
    
    // Critical section
    
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t thread_id;
    pthread_mutex_init(&mutex, NULL);
    
    pthread_create(&thread_id, NULL, thread_function, NULL);
    
    // Main program logic here
    
    pthread_join(thread_id, NULL);
    
    pthread_mutex_destroy(&mutex);
    
    return 0;
}

To compile the code, use the -pthread flag with gcc: gcc -pthread main.c -o main

JavaScript is a single-threaded scripting language that does not support multithreading.

Below is a simple JavaScript program that creates a thread (where the thread prints some text to standard output) indirectly by using worker and then waits for it to end:

// Equivalent JavaScript code using Web Workers
const worker = new Worker(URL.createObjectURL(new Blob([`
    self.onmessage = function(event) {
        console.log(event.data);
    };
`], { type: 'application/javascript' })));

worker.postMessage('Hello from a thread!');

The same code in C would be as follows:

#include <stdio.h>
#include <pthread.h>// Requires GCC

void *thread_function(void *arg) {
    printf("Hello from a thread!\n");
    return NULL;
}

int main() {
    pthread_t thread;
    pthread_create(&thread, NULL, thread_function, NULL);
    pthread_join(thread, NULL); // wait for thread to finish
    return 0;
}

In JavaScript, it's possible to send data as an argument to a thread:

const workerCode = `
self.onmessage = function(e) {
    let eventData = e.data;
    eventData += (" World!");
    console.log("Phrase: " + eventData);
};
`;

const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));

const data = "Hello";
worker.postMessage(data);

In C, the data is passed to the thread:

#include <stdio.h>
#include <pthread.h>// Requires GCC
#include <string.h>

void* thread_function(void* arg) {
    char* data = (char*)arg;
    strcat(data, " World!");
    return data;
}

int main() {
    char data[20] = "Hello";
    pthread_t thread;
    pthread_create(&thread, NULL, thread_function, (void*)data);
    void* result;
    pthread_join(thread, &result);
    printf("Phrase: %s\n", (char*)result);
    return 0;
}

Synchronization

When data is shared between threads, one needs to synchronize read-write access to the data in order to avoid corruption. In JavaScript:

let data = 0;
let workers = [];
let completedWorkers = 0;

for (let i = 0; i < 10; i++) {
    let worker = new Worker('data:text/javascript,' + encodeURIComponent(`
        let partialData = 0;
        for (let j = 0; j < 1000; j++) {
            partialData++;
        }
        self.postMessage(partialData);
    `));
    
    worker.onmessage = function(event) {
        data += event.data;
        completedWorkers++;
        if (completedWorkers === 10) {
            console.log(data);
            workers.forEach(function(worker) {
                worker.terminate();
            });
        }
    };
    
    workers.push(worker);
    worker.postMessage(null);
}

Using threading and synchronization mechanisms available in C:

#include <stdio.h>
#include <pthread.h> // Requires GCC

#define NUM_THREADS 10
#define NUM_INCREMENTS 1000

int data = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* thread_function(void* arg) {
    for (int i = 0; i < NUM_INCREMENTS; i++) {
        pthread_mutex_lock(&mutex);
        data++;
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, thread_function, NULL);
    }

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    printf("%d\n", data);

    return 0;
}

Producer-Consumer

The producer-consumer pattern is very common to distribute work between threads where data is passed from producing threads to consuming threads without the need for sharing or locking.

const workerCode = `
    self.onmessage = function() {
        const messages = [];
        for (let n = 1; n < 10; n++) {
            messages.push("Message #" + n);
        }
        self.postMessage(messages);
    };
`;

const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));

// The main thread acts as a consumer here
worker.onmessage = function(event) {
    const messages = event.data;
    messages.forEach(message => console.log(message));
};

// Start the worker
worker.postMessage(null);

A rough translation of the above JavaScript example in C would look as follows:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> // Requires GCC

#define BUFFER_SIZE 10

void* producer(void* arg) {
    for (int n = 1; n < 10; n++) {
        printf("Message #%d\n", n);
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, producer, NULL);

    // main thread acts as the consumer
    pthread_join(tid, NULL);

    return 0;
}

Testing

Test organization

JavaScript solutions use separate projects to host test code, irrespective of the test framework being used (Jest, Mocha, etc.) and the type of tests (unit or integration) being written. The test code therefore lives in a separate assembly than the application or library code being tested. In C, developers use a third party tool called CUnit for testing.

See also:

Running tests

CUnit supports running all tests in all registered suites, but individual tests or suites can also be run. During each run, the framework keeps track of the number of suites, tests, and assertions run, passed, and failed. Note that the results are cleared each time a test run is initiated (even if it fails).

For more information, see "Running Tests in CUnit".

Output in Tests

For very complex integration or end-to-end test, developers sometimes log what's happening during a test. The actual way they do this varies with each test framework in C.

Assertions

JavaScript users have multiple ways to assert, depending on the framework being used. For example, an assertion in Jest might look like:

test('something has the right length', () => {
    let value = "something";
    expect(value.length).toBe(9);
});

An example that only uses vanilla JavaScript:

function somethingIsTheRightLength() {
    let value = "something";
    console.assert(value.length === 9);
}

somethingIsTheRightLength();

C does not require a separate framework like CUnit for asserting.

Below is an example:

#include <assert.h>
#include <string.h>

int main() {
    void test_something_is_the_right_length() {
        const char *value = "something";
        assert(strlen(value) == 9);
    }

    test_something_is_the_right_length(); // Call the test function
    return 0;
}

The standard library does not offer anything in the direction of data-driven tests.

Mocking

There are libraries for C too, like umock_c, that can help with mocking. However, it is also possible to use conditional compilation.

The example below shows mocking of a stand-alone function var_os and returns the value of an environment variable. It conditionally imports a mocked version of the var_os function:

#include <stdio.h>

#ifdef TEST
    #define var_os var_os_mock
    char* var_os_mock() {
        return "Mocked Value";
    }
#else
    char* var_os() {
        return "Actual Value";
    }
#endif

void get_env() {
    char* value = var_os();
    printf("Environment Variable Value: %s\n", value);
}

int main() {
    #ifndef TEST
        get_env();
    #endif

    return 0;
}

Code coverage

There is sophisticated tooling for JavaScript when it comes to analyzing test code coverage, such as Jest.

In C, one can implement code coverage functionalities by utilizing tools like gcov or LLVM's coverage tools. These tools can help collect test code coverage data in C programs. By incorporating these tools into the C codebase, developers can achieve code coverage analysis.

Benchmarking

Running benchmarks in C is done via third party tools.

JavaScript users can make use of Benchmark.js library to benchmark methods and track their performance. The equivalent of Benchmark.js in C is a tool named ansibench.

As per its documentation, ansibench collects and stores statistical information from run to run and can automatically detect performance regressions as well as measuring optimizations.

Logging and Tracing

For most cases, console.log() is a good default choice for JavaScript, since it works with a variety of built-in and third-party logging providers. In JavaScript, a minimal example for structured logging could look like:

let day = "Thursday";
console.log("Hello ", day); // Hello Thursday.

In C, there are no built-in loggers. Developers use printf based methods to log.

Conditional Compilation

Both JavaScript and C are providing the possibility for compiling specific code based on external conditions.

JavaScript doesn't support conditional compilation natively. However, it is possible to use some third-party tool like babel-plugin-preprocessor in order to control conditional compilation.

//#if DEBUG
    console.log("Debug");
//#else
    console.log("Not debug");
//#endif

An example that uses vanilla JavaScript:

let isDebug = true;

if(isDebug)
{
    window.eval(`
    console.log("Debug");
    `);
} else {
    window.eval(`
    console.log("Not debug");
    `);
}

Conditional compilation in C allows developers to include or exclude sections of code during the compilation process based on certain conditions. This feature is particularly useful when different versions of a program need to be generated for various platforms or configurations without modifying the source code.

In C, conditional compilation is achieved using preprocessor directives, which are processed before the actual compilation of the code. The #ifdef, #ifndef, #else, #elif, and #endif directives are commonly used for conditional compilation.

Here is a simple example to illustrate conditional compilation in C:

#include <stdio.h>

#define DEBUG 1

int main() {
    #ifdef DEBUG
        printf("Debug mode is enabled.\n");
    #else
        printf("Debug mode is disabled.\n");
    #endif

    return 0;
}

Environment and Configuration

Accessing environment variables

JavaScript doesn't provide access to environment variables natively. However, some non-browser JavaScript runtimes, such as Node.js and Node provides access to environment variables.

In Node.js:

const name = "EXAMPLE_VARIABLE";

let value = process.env[name];
if (!value) {
    console.log(`Variable '${name}' not set.`);
} else {
    console.log(`Variable '${name}' set to '${value}'.`);
}

In Deno:

const name = "EXAMPLE_VARIABLE";

let value = Deno.env.get(name);
if (!value) {
    console.log(`Variable '${name}' not set.`);
} else {
    console.log(`Variable '${name}' set to '${value}'.`);
}

In C programming, environmental variables can be accessed using the getenv() function provided by the standard library <stdlib.h>. This function allows a program to retrieve the value of a specific environmental variable by providing its name as an argument.

Here is a simple example demonstrating how to retrieve the value of an environmental variable named PATH:

#include <stdio.h>
#include <stdlib.h>

int main() {
    char *path_value = getenv("PATH");

    if (path_value != NULL) {
        printf("The value of PATH is: %s\n", path_value);
    } else {
        printf("PATH is not set.\n");
    }

    return 0;
}

In C, accessing environment variables at compile time involves utilizing preprocessor directives and macros to incorporate the values of environment variables during the compilation phase. This process allows for the configuration of the program based on the environment where it will run without the need for runtime modifications.

One common approach to achieve this functionality is by using the -D flag in the compiler command to define a macro with the value of the desired environment variable. For instance, consider an environment variable MY_ENV_VAR that you want to access at compile time. You can pass this variable's value to the compiler using the -D flag as follows:

#include <stdio.h>

#ifndef MY_ENV_VAR
    #define MY_ENV_VAR "default_value"
#endif

int main() {
    printf("Value of MY_ENV_VAR: %s\n", MY_ENV_VAR);
    return 0;
}

When compiling the program, it is possible to set the value of MY_ENV_VAR by defining it during compilation:

gcc -o myprogram myprogram.c -DMY_ENV_VAR='"custom_value"'

Configuration

JavaScript doesn't support configurations, neither nor C.

LINQ

This section discusses LINQ within the context and for the purpose of querying or transforming sequences and typically collections like lists, sets and dictionaries.

Enumerable items

The equivalent of enumerable items in C is enum. In JavaScript, there is forEach:

let values = [1, 2, 3, 4, 5];
let output = '';

values.forEach((value, index) => {
    if (index > 0)
        output += ', ';
    output += value;
});

console.log(output); // Outputs: 1, 2, 3, 4, 5

In C, the equivalent is simply for:

u#include <stdio.h>
#include <string.h> // Include the string.h header file

int main() {
    int values[] = {1, 2, 3, 4, 5};
    char output[50] = "";
    
    for (int i = 0; i < 5; i++) {
        if (i > 0)
            sprintf(output + strlen(output), ", ");
        sprintf(output + strlen(output), "%d", values[i]);
    }

    printf("%s\n", output); // Outputs: 1, 2, 3, 4, 5

    return 0;
}

The for loop over an iterable essentially gets desuraged to the following:

#include <stdio.h>
#include <string.h>
int main() {
    int values[] = {1, 2, 3, 4, 5};
    char output[50] = "";
    int i;

    for (i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
        if (i > 0) {
            sprintf(output + strlen(output), ", ");
        }
        sprintf(output + strlen(output), "%d", values[i]);
    }

    printf("%s\n", output);
    return 0;
}

Operators

JavaScript and C don't natively support LINQ, but there is a project called LINQ.js that implements LINQ in C# for JavaScript, and a project called linq4c that implements LINQ in C# for C.

Operators in LINQ are implemented in the form of LINQ.js extension methods that can be chained together to form a set of operations, with the most common forming a query over some sort of data source. LINQ.js also offers a SQL-inspired query syntax with clauses like from, where, select, join and others that can serve as an alternative or a companion to method chaining. Many imperative loops can be re-written as much more expressive and composable queries in LINQ.

Deferred execution (laziness)

Many operators in LINQ are designed to be lazy such that they only do work when absolutely required. This enables composition or chaining of several operations/methods without causing any side-effects.

In both cases, this allows infinite sequences to be represented, where the underlying sequence is infinite, but the developer decides how the sequence should be terminated. The following example shows this in JavaScript:

function* infiniteRange() {
    for (let i = 0; ; ++i) {
        yield i;
    }
}

for (let x of infiniteRange()) {
    if (x < 5) {
        console.log(x); // Prints "0 1 2 3 4"
    } else {
        break;
    }
}

C supports the same concept through for:

#include <stdio.h>

int main() {
    int value;
    for (value = 0; value < 5; value++) {
        printf("%d ", value); // Prints "0 1 2 3 4"
    }
    return 0;
}

Iterator Methods (yield)

JavaScript has the yield keword that enables the developer to quickly write an iterator method. C doesn't support coroutines natively, but there is a tutorial of implementing coroutines in C.

Meta Programming

Metaprogramming can be seen as a way of writing code that writes/generates other code.

JavaScript has the concept of metaprogramming, but it refers to intercepting and defining basic language operations, which is different from metaprogramming in C, C# or Rust. There is a JavaScript source generator called hygen, but it does not call itself a "metaprogramming tool".

C does not support metaprogramming natively, however, third party tools, like metalang99, exists.

C does not support reflection.

Function-like macros

Function-like macros in C are in the following form: #define

The following code snippet defines a function-like macro named print_something, which is generating a print_it method for printing the "Something" string.

#include <stdio.h>

#define print_something() printf("Something\n")

int main() {
    print_something();
    return 0;
}

Derive macros

Derive macros are not supported in C and JavaScript.

Attribute macros

Attribute macros are not supported in C and JavaScript.

Asynchronous Programming

Both JavaScript and C support asynchronous programming models, which look similar to each other with respect to their usage. The following example shows, on a very high level, how async code looks like in JavaScript:

async function printDelayed(message, cancellationToken) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    return `Message: ${message}`;
}

C code is structured differently:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h> // Requires GCC
#include <unistd.h>

void* printDelayed(void* args) {
    char* message = (char*)args;
    sleep(1); // Simulating a delay of 1 second
    char* result = malloc(strlen(message) + 10); // Allocating memory for the result
    sprintf(result, "Message: %s", message);
    return result;
}

int main() {
    char* message = "Hello, World!";
    pthread_t tid;
    void* result;

    pthread_create(&tid, NULL, printDelayed, (void*)message);
    pthread_join(tid, &result);

    printf("%s\n", (char*)result);
    free(result); // Freeing allocated memory
    return 0;
}

See also:

Executing tasks

From the following example the PrintDelayed method executes, even though it is not awaited:

let cancellationToken = undefined; 
printDelayed("message", cancellationToken); // Prints "message" after a second.
await new Promise(resolve => setTimeout(resolve, 2000));

async function printDelayed(message, cancellationToken) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(message);
}

In C:

#include <stdio.h>

#ifdef _WIN32
#include <windows.h>
#define sleep(x) Sleep(x * 1000)
#else
#include <unistd.h>
#endif

void printDelayed(char* message) {
    sleep(1); // Delay for 1 second
    printf("%s\n", message);
}

int main() {
    char* message = "message";
    printDelayed(message); // Prints "message" after a second.
    sleep(2); // Delay for 2 seconds
    return 0;
}

Task cancellation

The previous JavaScript examples included passing a CancellationToken to asynchronous methods, as is considered best practice in JavaScript. CancellationTokens can be used to abort an asynchronous operation.

In C language, the concept ofcancellation can be implemented using custom structures and functions.

Executing multiple Tasks

In JavaScript, Promise.race is frequently used to handle the execution of multiple tasks.

Promise.race completes as soon as any task completes.

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

const delayMessage = async (delayTime) => {
    await delay(delayTime);
    return `Waited ${delayTime / 1000} second(s).`;
};

const delay1 = delayMessage(1000);
const delay2 = delayMessage(2000);

Promise.race([delay1, delay2]).then(result => {
    console.log(result); // Output: Waited 1 second(s).
});

In C, to achieve similar functionality to Promise.race in JavaScript, one can use a combination of threads and synchronization mechanisms like mutexes or semaphores. By creating multiple threads that perform tasks concurrently and using synchronization to determine which thread finishes first, a similar behavior to Promise.race can be achieved in C. Below is a simplified example in C:

#include <stdio.h>
#include <pthread.h> // Requires GCC
#include <unistd.h>

void* delayMessage(void* delayTime) {
    int ms = *((int*)delayTime);
    usleep(ms * 1000); // Convert milliseconds to microseconds
    char* message = malloc(100 * sizeof(char));
    sprintf(message, "Waited %d second(s).", ms / 1000);
    return message;
}

int main() {
    pthread_t thread1, thread2;
    int delay1 = 1000;
    int delay2 = 2000;
    char* result1;
    char* result2;

    pthread_create(&thread1, NULL, delayMessage, (void*)&delay1);
    pthread_create(&thread2, NULL, delayMessage, (void*)&delay2);

    pthread_join(thread1, (void**)&result1);
    pthread_join(thread2, (void**)&result2);

    printf("%s\n", result1); // Output: Waited 1 second(s).

    free(result1);
    free(result2);

    return 0;
}

Multiple consumers

In JavaScript a Promise can be used across multiple consumers. All of them can await the task and get notified when the task is completed or failed.

In C:

#include <stdio.h>
#include <stdlib.h>

#ifdef _WIN32
#include <windows.h> // For Sleep(), DWORD
#else
#include <unistd.h> // For sleep()
#include <sys/types.h> // For pid_t
#include <sys/wait.h> // For wait()
#endif

void background_operation() {
#ifdef _WIN32
    Sleep(2000);
#else
    sleep(2);
#endif
    printf("Background operation completed.\n");
}

#ifdef _WIN32
BOOL WINAPI signal_handler(DWORD dwCtrlType) {
    if (dwCtrlType == CTRL_C_EVENT) {
        printf("Signal received. Cancelling background operation.\n");
        exit(0);
    }
    return TRUE;
}
#else
void signal_handler(int signal) {
    if (signal == SIGINT) {
        printf("Signal received. Cancelling background operation.\n");
        exit(0);
    }
}
#endif

int main() {
#ifdef _WIN32
    SetConsoleCtrlHandler(signal_handler, TRUE);
#else
    signal(SIGINT, signal_handler);
#endif

    DWORD pid = GetCurrentProcessId(); // Get current process ID
    printf("Process ID: %lu\n", pid);

    // Fork and perform background operation
    // ...

    wait(NULL);
    printf("Parent process waiting for child process to finish.\n");

    return 0;
}

Asynchronous iteration

C does not yet have an API for asynchronous iteration in the standard library.

In JavaScript, writing async iterators has comparable syntax to when writing synchronous iterators:

async function* RangeAsync(start, count) {
    for (let i = 0; i < count; i++) {
        await new Promise(resolve => setTimeout(resolve, i * 1000));
        yield start + i;
    }
}

(async () => {
    for await (const item of RangeAsync(10, 3)) {
        console.log(item + " "); // Prints "10 11 12".
    }
})();

In C:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

typedef struct {
    int start;
    int count;
} Range;

void RangeAsync(Range range) {
    for (int i = 0; i < range.count; i++) {
        usleep(i * 1000000); // Simulating async delay in microseconds
        printf("%d ", range.start + i);
    }
}

int main() {
    Range range = {10, 3};
    RangeAsync(range);
    return 0;
}

Project Structure

The JavaScript standard does not specify a specification for the structure of the project. Generally speaking, all the files in a JavaScript library are usually placed in a folder named after the library. The following is a common JavaScript project specification:

.
+-- src/
|   +-- project1.js
+-- styles/
|   +-- style1.js
+-- examples/
|   +-- some-example.js
+-- tests/
    +-- some-integration-test.js

The C standard does not specify a specification for the structure of the project.

Managing large projects

For very large projects in C, different IDEs and different toolchains have different ways to manage large projects.

Managing dependency versions

There is no concept of dependency in the C standard, different IDEs and different toolchains have different ways to manage dependency versions.

Compilation and Building

JavaScript CLI

There is no concept of CLI in the JavaScript standard. People often use non-browser runtimes such as Node.js and Deno to act as CLIs.

There is no concept of CLI in the C standard, different IDEs and different toolchains have different CLIs.

Building

When building JavaScript, the scripts coming from dependent packages are generally co-located with the project's output assembly. C compilers compiles the project sources, except the C compiler statically links all code into a single, platform-dependent, binary.

Developers use different ways to prepare a JavaScript executable for distribution, either as a framework-dependent deployment (FDD) or self-contained deployment (SCD). In Rust, there is no way to let the build output already contains a single, platform-dependent binary for each target.

In C, the build output is, again, a platform-dependent, compiled library for each library target.

Dependencies

There is no concept of dependency in the JavaScript standard. However, some JavaScript runtimes, such as Node.js and Deno, have the concept of dependencies. In Node.js and Deno, the contents of a project file (package.json) define the build options and dependencies. A typical project file will look like:

{
  "name": "your-project-name",
  "version": "1.0.0",
  "description": "Your project description",
  "dependencies": {
    "linq": "4.0.3"
  }
}

There is no concept of dependency in the C standard, different IDEs and different toolchains have different ways to manage dependencies.

Packages

There is no concept of packages in the JavaScript standard. However, some JavaScript runtimes, such as Node.js, have the concept of packages. NPM is most commonly used to install packages for Node.js, and various tools supported it. For example, adding a Node.js package reference with the Node,js CLI will add the dependency to the project file:

npm install linq

The most common package registry for Node.js is npmjs.com .

There is no concept of packages in the C standard, different IDEs and different toolchains have different ways to manage packages.

Static code analysis

ESLint is an analyzer that provide code quality as well as code-style analysis. The equivalent linting tool in C is Clang Static Analyzer or other tools.

File Operations

This chapter introduce file operations in C and JavaScript.

JavaScript has no built-in file operation APIs, so third party JavaScript runtimes are required.

Text File Operations

Write and read a text file in C:

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file;
    char text[] = "sample text";
    
    // Write to file
    file = fopen("test.txt", "w");
    if (file == NULL) {
        printf("Error opening file!\n");
        exit(1);
    }
    fprintf(file, "%s", text);
    fclose(file);
    
    // Read from file
    char buffer[255];
    file = fopen("test.txt", "r");
    if (file == NULL) {
        printf("Error opening file!\n");
        exit(1);
    }
    fscanf(file, "%s", buffer);
    printf("Content of test.txt: %s\n", buffer);
    fclose(file);
    
    return 0;
}

In Node.js:

const fs = require('fs');

// Write to file
fs.writeFileSync('test.txt', 'sample text');

// Read from file
const data = fs.readFileSync('test.txt', 'utf8');
console.log(data);

In Deno:

// to run the program, execute `deno run --allow-all text-operations.js`
const text = "sample text";
const encoder = new TextEncoder();
const data = encoder.encode(text);

await Deno.writeFile("test.txt", data);

const file = await Deno.open("test.txt");
const fileContent = new Uint8Array(100);
await Deno.read(file.rid, fileContent);

const decoder = new TextDecoder();
const decodedText = decoder.decode(fileContent);

console.log(decodedText);

Deno.close(file.rid);

Binary File Operations

In the following example, the binary data of the ASCII character a will be written to test.bin, and the content of test.bin will be read and displayed on the console. The size of the binary file is determined at runtime.

In C:

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file;
    char data = 'a';

    // Writing binary data to test.bin
    file = fopen("test.bin", "wb");
    fwrite(&data, sizeof(char), 1, file);
    fclose(file);

    // Reading and displaying binary data from test.bin
    file = fopen("test.bin", "rb");
    fseek(file, 0, SEEK_END);
    long fileSize = ftell(file);
    rewind(file);

    char *buffer = (char *)malloc(fileSize);
    fread(buffer, sizeof(char), fileSize, file);
    fclose(file);

    for (int i = 0; i < fileSize; i++) {
        printf("%c", buffer[i]);
    }

    free(buffer);
    return 0;
}

In Node.js:

const fs = require('fs');

const data = Buffer.from('a', 'ascii');

fs.writeFile('test.bin', data, (err) => {
  if (err) throw err;

  fs.readFile('test.bin', (err, data) => {
    if (err) throw err;
    console.log(data.toString('ascii'));
  });
});

In Deno:

// to run the program, execute `deno run --allow-all binary-operations.js`
const encoder = new TextEncoder();
const data = encoder.encode('a');

await Deno.writeFile("test.bin", data);

const file = await Deno.open("test.bin");
const content = new Uint8Array(await Deno.readAll(file));
Deno.close(file.rid); // Close the file using the file descriptor

const decoder = new TextDecoder();
console.log(decoder.decode(content));

Copying File Operations

The following code will copy test.txt to test2.txt.

In C:

#include <stdio.h>

int main() {
    FILE *source, *target;
    char ch;

    source = fopen("test.txt", "r");
    if (source == NULL) {
        printf("Error opening source file.\n");
        return 1;
    }

    target = fopen("test2.txt", "w");
    if (target == NULL) {
        fclose(source);
        printf("Error opening target file.\n");
        return 1;
    }

    while ((ch = fgetc(source)) != EOF) {
        fputc(ch, target);
    }

    printf("File copied successfully.\n");

    fclose(source);
    fclose(target);

    return 0;
}

In Node.js:

const fs = require('fs');

fs.copyFile('test.txt', 'test2.txt', (err) => {
  if (err) throw err;
  console.log('test.txt was copied to test2.txt');
});

In Deno:

// to run the program, execute `deno run --allow-all copying-operations.js`
const sourceFile = await Deno.open('test.txt');
const destinationFile = await Deno.create('test2.txt');

await Deno.copy(sourceFile, destinationFile);

sourceFile.close();
destinationFile.close();

console.log('File copied successfully!');

Renaming File Operations

The following code will rename old_name.txt to new_name.txt.

In C:

#include <stdio.h>

int main() {
    const char *old_name = "old_name.txt";
    const char *new_name = "new_name.txt";

    if (rename(old_name, new_name) == 0) {
        printf("File renamed successfully.\n");
    } else {
        perror("Error renaming file");
    }

    return 0;
}

In Node.js:

const fs = require('fs');

const oldPath = 'old_name.txt';
const newPath = 'new_name.txt';

fs.rename(oldPath, newPath, (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('File renamed successfully!');
});

In Deno:

// to run the program, execute `deno run --allow-all renaming-operations.js`

// Define the old and new file names
const oldName = "old_name.txt";
const newName = "new_name.txt";

// Rename the file synchronously
Deno.renameSync(oldName, newName);

console.log(`File ${oldName} has been renamed to ${newName} successfully.`);

Moving File Operations

The following code will move test.txt to ./test_folder/test.txt.

In C:

#include <stdio.h>

int main() {
    if(rename("test.txt", "./test_folder/test.txt") == 0) {
        printf("File moved successfully.\n");
    } else {
        perror("Error moving file");
    }
    return 0;
}

In Node.js:

const fs = require('fs');

const sourcePath = 'test.txt';
const destinationPath = './test_folder/test.txt';

fs.rename(sourcePath, destinationPath, (err) => {
    if (err) {
        console.error(`Error moving the file: ${err}`);
    } else {
        console.log('File moved successfully!');
    }
});

In Deno:

// to run the program, execute `deno run --allow-all moving-operations.js`
const oldFilePath = 'test.txt';
const newFilePath = './test_folder/test.txt';

await Deno.rename(oldFilePath, newFilePath);
console.log(`File moved successfully from ${oldFilePath} to ${newFilePath}`);

Deleting File Operations

The following code will delete test.txt.

In C:

#include <stdio.h>

int main() {
    if (remove("test.txt") == 0) {
        printf("File deleted successfully.\n");
    } else {
        printf("Error deleting the file.\n");
    }
    return 0;
}

In Node.js:

const fs = require('fs');

const filePath = 'test.txt';

fs.unlink(filePath, (err) => {
    if (err) {
        console.error(`Error deleting the file: ${err}`);
        return;
    }
    console.log('File deleted successfully');
});

In Deno:

// to run the program, execute `deno run --allow-all deleting-operations.js`
try {
  Deno.removeSync("test.txt");
  console.log("File 'test.txt' has been deleted successfully.");
} catch (error) {
  console.error("An error occurred:", error);
}

Folder Operations

The following code will create a folder called test_folder.

In C:

#include <stdio.h>

#ifdef _WIN32
    #include <direct.h>
    #define CREATE_DIR(path) _mkdir(path)
#else
    #include <sys/stat.h>
    #define CREATE_DIR(path) mkdir(path, 0777)
#endif

int main() {
    char* folderName = "test_folder";

    if (CREATE_DIR(folderName) == 0) {
        printf("Folder created successfully.\n");
    } else {
        printf("Failed to create folder.\n");
    }

    return 0;
}

In Node.js:

const fs = require('fs');

const folderName = 'test_folder';

fs.mkdir(folderName, (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log(`Folder '${folderName}' created successfully.`);
});

In Deno:

// to run the program, execute `deno run --allow-all folder-operations.js`
const { mkdir } = Deno;

const folderName = "test_folder";

try {
  await mkdir(folderName);
  console.log(`Folder '${folderName}' created successfully.`);
} catch (error) {
  console.error(`Error creating folder: ${error.message}`);
}